订阅更新

用Pandas读了个5GB的CSV内存直接爆了——大文件处理策略

Python手册 第 6 题 约 9 分钟读完

本文目录
  1. 先说结论
  2. 策略一:chunksize分块读取
  3. 策略二:dtype优化——最有效的单步优化
  4. 策略三:只读需要的列
  5. 策略四:换引擎——超过内存的数据
  6. 实战经验:3.5GB CSV的处理方案
  7. 文件格式的选择——CSV vs Parquet
  8. 面试高频追问
  9. 总结

先说结论

Pandas处理大文件有四种策略:① chunksize分块读取(最简单);② dtype优化减少内存(最有效的单步优化);③ 只读需要的列(usecols参数);④ 换引擎——用Polars/DuckDB/PySpark替代Pandas处理大于内存的文件。数据量小于2GB优先优化Pandas,大于2GB考虑换引擎。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

策略一:chunksize分块读取

# 不要一次性读取整个文件
# df = pd.read_csv('huge.csv')  # MemoryError!
 
# 分块读取,每次处理100万行
chunks = pd.read_csv('huge.csv', chunksize=1_000_000)
 
results = []
for chunk in chunks:
    # 每个chunk是一个100万行的DataFrame
    # 在这里做过滤/聚合,只保留需要的结果
    filtered = chunk[chunk['amount'] > 0]
    summary = filtered.groupby('country')['amount'].sum()
    results.append(summary)
 
# 合并所有chunk的结果
final = pd.concat(results).groupby(level=0).sum()
 
# 这样内存中同时只有一个chunk(100万行 ≈ 几百MB)
# 而不是整个文件(几个GB)
 
# 注意:chunksize不适合需要全量排序或全局JOIN的操作
# 因为每个chunk只有部分数据,无法做全局操作

策略二:dtype优化——最有效的单步优化

# Pandas默认用int64/float64/object,非常浪费内存
 
# 优化前:Pandas自动推断类型
df = pd.read_csv('data.csv')
df.info(memory_usage='deep')  # 800MB
 
# 优化后:手动指定更小的类型
df = pd.read_csv('data.csv', dtype={
    'user_id': 'int32',       # int64(8字节) => int32(4字节)
    'amount': 'float32',      # float64(8字节) => float32(4字节)
    'country': 'category',    # object(可变) => category(枚举编码)
    'event_name': 'category', # 重复字符串用category最省
    'is_paid': 'bool',        # object => bool(1字节)
})
df.info(memory_usage='deep')  # 250MB(省了70%!)
 
# category类型的原理:
# country列有50个不同值,100万行
# object类型:每行一个字符串对象 ≈ 100万 × 60字节 = 60MB
# category:50个字符串 + 100万个int8编码 = 3KB + 1MB ≈ 1MB
# 省了60倍!

策略三:只读需要的列

# CSV有50列,你只需要5列
 
# 错误:读全部列再选
df = pd.read_csv('data.csv')  # 读了50列
df = df[['user_id', 'country', 'amount', 'event_name', 'dt']]
# 虽然最终只保留5列,但读取时全部50列都进了内存
 
# 正确:只读需要的列
df = pd.read_csv('data.csv',
    usecols=['user_id', 'country', 'amount', 'event_name', 'dt'])
# 只读5列,IO量和内存都只有原来的1/10
 
# 对Parquet格式效果更好(列式存储)
df = pd.read_parquet('data.parquet',
    columns=['user_id', 'country', 'amount'])
# Parquet的列裁剪在文件层面就跳过了不需要的列
# 比CSV的usecols更快(CSV还是要扫描整行才能提取某列)

策略四:换引擎——超过内存的数据

工具适用数据量核心优势大数据场景
Pandas小于1-2GB生态最成熟小数据分析、结果后处理
Polars小于10GB快5-10倍,惰性求值中等数据的高性能处理
DuckDB小于100GBSQL接口,可查文件直接SQL查Parquet/CSV
PySparkGB到TB级分布式集群处理大规模ETL
# DuckDB:直接用SQL查大文件(不需要全部加载到内存)
import duckdb
 
# 直接查5GB的Parquet文件
result = duckdb.sql("""
    SELECT country, SUM(amount) AS total
    FROM 'huge_data.parquet'
    WHERE dt = '2024-01-15'
    GROUP BY country
    ORDER BY total DESC
    LIMIT 20
""").df()  # 返回Pandas DataFrame
 
# DuckDB使用流式处理+磁盘溢出
# 不需要把整个文件加载到内存
# 5GB数据只需要几百MB内存
 
# Polars的惰性求值
import polars as pl
result = (pl.scan_parquet('huge_data.parquet')  # lazy,不立即加载
    .filter(pl.col('dt') == '2024-01-15')
    .group_by('country')
    .agg(pl.col('amount').sum())
    .sort('amount', descending=True)
    .head(20)
    .collect())  # 这里才真正执行

实战经验:3.5GB CSV的处理方案

在某短剧出海公司,运营需要分析一批广告投放数据——从MaxCompute导出的3.5GB CSV(约2000万行、40列)。分析师的笔记本只有16GB内存。

方案一(Pandas优化):usecols只读8列 + dtype指定category/float32 + chunksize=50万。内存从爆掉降到约1.5GB,但处理速度较慢(每个chunk聚合后合并)。

方案二(DuckDB,最终采用):直接用DuckDB的SQL查Parquet文件(先把CSV转成Parquet格式),GROUP BY+聚合一条SQL搞定,内存只用了400MB,3秒出结果。

教训:2026年了,处理超过1GB的数据不一定非得用Pandas。DuckDB和Polars在单机上的体验已经非常好了,很多场景比Pandas更快更省内存。

文件格式的选择——CSV vs Parquet

维度CSVParquet
格式文本(行式)二进制(列式)
体积大(无压缩)小(压缩后通常是CSV的1/5-1/10)
列裁剪不支持(必须扫描整行)支持(只读需要的列)
类型信息无(需要推断)有(存在文件元数据中)
读取速度慢(文本解析开销)快(二进制直接映射)
适用场景数据交换、人可读分析处理、长期存储

建议:数据分析和ETL场景优先用Parquet格式。CSV只用于临时的数据交换(导出给Excel等)。同样的数据Parquet比CSV小5-10倍、读取快3-5倍、还支持列裁剪和谓词下推。

面试高频追问

追问一:Pandas的read_csv有哪些优化参数?

关键参数:① usecols——只读需要的列;② dtype——指定列类型减少内存;③ chunksize——分块读取;④ nrows——只读前N行(快速预览);⑤ parse_dates——指定日期列避免后续转换;⑥ na_values——指定缺失值标识;⑦ engine='pyarrow'——用PyArrow引擎读取(比默认的C引擎快2倍)。

追问二:Pandas 2.0有什么重要变化?

Pandas 2.0支持了PyArrow后端——df = pd.read_csv('data.csv', dtype_backend='pyarrow')。用Arrow格式存储数据而不是NumPy数组,内存更省(特别是字符串列)、支持更多原生类型(date/time/decimal)、和PySpark/Polars的Arrow生态互通。建议新项目默认开启PyArrow后端。

追问三:DuckDB和SQLite有什么区别?

两者都是嵌入式数据库,但定位不同。SQLite是事务型数据库(OLTP),擅长点查和小数据量CRUD。DuckDB是分析型数据库(OLAP),擅长大数据量的聚合查询,支持直接查Parquet/CSV文件。DuckDB的分析性能比SQLite快10-100倍。可以理解为'DuckDB是单机版的Doris'。

总结

大文件处理面试讲四种策略按优先级排序:① dtype优化(category/float32减少内存70%,最简单有效);② usecols只读需要的列;③ chunksize分块读取;④ 换引擎(DuckDB/Polars处理GB级数据比Pandas更高效)。文件格式优先Parquet而不是CSV。能讲出3.5GB CSV从MemoryError到DuckDB 3秒出结果的实际方案,面试官会觉得你有实际的大文件处理经验。

下一篇聊Python装饰器:Airflow的@dag装饰器是什么原理?装饰器到底是什么语法糖?

你们怎么处理超过内存的数据文件?用DuckDB或Polars了吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,只讲大数据面试干货

刷题进度保存在当前浏览器