处理百亿级CSV必须用pl.scan_csv()而非pl.read_csv(),因后者会全量加载内存导致崩溃,前者仅建执行计划并支持谓词下推;需显式指定dtypes、合理使用过滤与聚合表达式,并避免过早.collect()。

百亿级CSV必须用pl.scan_csv(),别碰pl.read_csv()
直接结论:处理超千万行CSV时,pl.read_csv()和pl.scan_csv()不是“快慢之分”,而是“能不能跑通”的区别。pl.read_csv()会把整份文件一次性加载进内存,百亿行CSV轻松吃掉几百GB RAM;pl.scan_csv()只建执行计划,真正读取发生在.collect()那一刻,且支持谓词下推——过滤、列选择、聚合都可能被提前到扫描阶段执行。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 永远显式传
dtypes,比如{"user_id": pl.UInt64, "ts": pl.Datetime};不指定会导致Polars扫描全量样本推断类型,启动慢且易出错 - 有header但列名含空格或特殊字符?用
new_columns重命名:pl.scan_csv("x.csv", new_columns=["a", "b"]),别依赖infer_schema_length - 加
low_memory=True启用流式解析,避免临时缓冲区膨胀;配合row_count_name="idx"可省掉后续with_row_count()的额外遍历
filter()和group_by()写法决定是否真快
Polars的filter(pl.col("x") > 100)不是语法糖,它是编译成Rust指令的向量化操作;而Pandas的df[df.x > 100]是Python层逐行判断,解释器开销无法避免。写错一个点,就退化回单线程循环。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 字符串过滤优先用
str.starts_with()或str.ends_with(),它们能触发SIMD加速;str.contains()是通用模糊匹配,性能差一截 -
group_by("key").agg()默认走哈希分组;若key已排序且需保持输出顺序,加maintain_order=True跳过重排 - 绝对不要在
agg()里用pl.col("x").apply(...)——这会让整个聚合退化为Python循环;所有计算必须用内置表达式,如pl.mean("x")、pl.max("y")
LazyFrame的.collect()不是收尾动作,是内存峰值开关
惰性模式下,.filter()、.select()、.group_by().agg()都不触发计算,只生成物理执行计划。真正把数据拉进内存的,只有.collect()这一下。很多人误以为“链式写完再collect就行”,结果中间反复调用.collect(),导致多份DataFrame同时驻留内存。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 调试时用
.explain()看物理执行计划,确认filter是否被下推、是否用了并行扫描 - 中间结果别反复
.collect(),比如先df1 = q.collect()再df2 = df1.filter().collect()——这会让两份数据同时占内存 - 如果最终要写Parquet,直接
.collect().write_parquet("out.parq"),别拆成两步
别幻想“一键替换”,关键在查询计划设计
真正卡住人的从来不是语法迁移,而是习惯性把Pandas的“先加载再处理”思维带进来。Polars不接受“我先读进来再说”,它要求你在写第一行pl.scan_csv()时就想清楚:哪几列真需要?哪些条件能下推?聚合是否可合并?漏掉任何一个,都可能让性能优势归零。
最容易被忽略的一点:Polars和DuckDB不是竞品,而是互补。读大CSV、做复杂清洗用Polars LazyFrame;需要高频即席查询或窗口函数时,把结果送进DuckDB;最后画图、对接BI工具,再转回pandas。三者混用,比死守一个库更省事也更快。



















