pandas.to_parquet() 处理千万级数据易OOM或极慢,因默认将全量DataFrame加载内存;应改用pyarrow分块写入、设row_group_size、ZSTD压缩及use_dictionary控制,并注意时区、空值、文件命名问题。

用 pandas.to_parquet() 直接写入千万级数据大概率会 OOM 或极慢——核心问题不在 Parquet 本身,而在默认参数没适配大数据场景。
为什么 to_parquet() 默认行为在海量数据下失效
默认用 engine="pyarrow",但不显式控制分块、压缩和内存缓冲时,pandas 会把整个 DataFrame 加载进内存再序列化。遇到 500 万行以上、宽表(>100 列)或含长文本列的数据,很容易触发 MemoryError 或 GC 频繁卡死。
常见错误现象:MemoryError、进程被系统 kill、写入耗时从秒级飙升到小时级、生成的 Parquet 文件意外只有单个 row group(丧失查询剪枝能力)。
关键原因:未启用分块写入(chunked write)、未设 compression、未调优 use_dictionary 和 use_deprecated_int96_timestamps 等底层参数。
立即学习“Python免费学习笔记(深入)”;
用 pyarrow.parquet.write_table() 手动分块写入
绕过 pandas 的中间层,直接用 PyArrow 控制写入粒度。适合已清洗好、存在内存中但不宜全量加载的 DataFrame,或需流式处理的场景。
- 先用
pa.Table.from_pandas(df)转换,但别一次性转全部——按行数切片,例如每 10 万行一批 - 用
pa.parquet.write_table()的row_group_size参数(如row_group_size=100_000)强制分组,避免单 group 过大 - 显式指定压缩:
compression="ZSTD"(比默认"SNAPPY"压缩率高、解压快,PyArrow ≥ 8.0 支持) - 对字符串列关闭字典编码可省内存:
use_dictionary=False(尤其当唯一值占比 >30% 时)
示例片段:
import pyarrow as pa
import pyarrow.parquet as pq
<h1>df 是你清洗后的 chunk(例如 df.iloc[0:100000])</h1><p>table = pa.Table.from_pandas(df)
pq.write_table(
table,
"output/part-001.parquet",
row_group_size=100_000,
compression="ZSTD",
use_dictionary={"text_col": False} # 按列控制
)用 Dask 处理超大 CSV/JSON 清洗后直接存 Parquet
如果原始数据根本装不下内存(比如 10GB+ CSV),pandas 先读再写是死路一条。Dask DataFrame 的 to_parquet() 天然支持分块、分区、并行写入,且不强制全量驻留内存。
- 清洗操作(
dropna、map、astype)要尽量用 Dask 原生方法,避免.compute()提前触发计算 - 写入时设
partition_on=["date"]可按列值自动建子目录,后续 Spark/Flink 查询能跳过无关分区 - 必须指定
write_index=False(Dask 默认带 index,Parquet 不需要,且浪费空间) - 用
schema="infer"或提前传schema=pa.schema(...),避免每块推断 schema 带来的开销和类型不一致
注意:dask.dataframe.to_parquet() 底层仍调 PyArrow,但调度层帮你管了 chunk size、线程数(num_workers)和内存限制(memory_limit)。
容易被忽略的三个硬伤点
第一,时间列时区丢失:datetime64[ns] 列若含 tz-aware 数据,默认写入会转成 UTC 且丢时区信息。必须加 use_deprecated_int96_timestamps=False + 显式 timestamp_as_object=False,或提前用 dt.tz_localize(None)。
第二,空字符串和 NaN 混存:pandas 把空字符串 "" 和 np.nan 都映射为 Parquet 的 NULL,但读回时可能全变 None。清洗阶段统一转成 pd.NA 并设 dtype="string",写入时加 use_dictionary=True 保语义。
第三,文件名不带哈希或序号:直接写 "data.parquet" 会被覆盖。务必用 f"data-{i:04d}.parquet" 或依赖 Dask 自动命名,否则重跑任务时数据静默丢失。


















