7.1 为什么要惰性
Eager 模式下,每一步都立刻执行、物化中间结果。假设你从一个 100 列、上亿行的 Parquet 里只想要"某地区 2 列的汇总",Eager 会先把全部读进内存再过滤——浪费巨大。Lazy 则先记录你要做什么,交给优化器统筹后,只读需要的列、只扫需要的行。
- LazyFrame
- 惰性的 DataFrame。所有操作(filter/select/join...)只是往查询计划里加节点,不真正计算。由
df.lazy()或scan_*得到。 - 查询计划(Query Plan)
- 一棵描述"如何得到结果"的操作树。优化器会重写这棵树,使其等价但更高效。
- collect()
- 触发实际执行的方法。此刻优化器介入、生成物理计划、多线程跑完,返回一个真正的 DataFrame。
- 谓词下推(Predicate Pushdown)
- 把
filter条件尽量"下推"到数据读取阶段,读文件时就跳过不满足的行/行组,大幅减少 IO。 - 投影下推(Projection Pushdown)
- 把"只需要哪些列"下推到读取阶段,从磁盘只读用到的列。列式格式(Parquet)配合它效果拔群。
- 公共子表达式消除(CSE)
- 识别重复计算的子表达式只算一次,结果复用。
- streaming(流式引擎)
- 分批(chunk)执行查询,内存里只保留一小部分数据,从而处理大于内存的数据集。
7.2 scan_* 惰性读取
import polars as pl
# scan 不读数据,只拿到 schema 与 LazyFrame
lf = pl.scan_parquet("huge.parquet")
result = (
lf
.filter(pl.col("region") == "华东") # 会被下推
.group_by("category")
.agg(pl.col("sales").sum())
.collect() # 此刻才真正读+算
)
# 也可 scan 一批文件(通配符)
lf = pl.scan_csv("logs/2026-*.csv")
7.3 查看查询计划:explain
lf = (
pl.scan_parquet("huge.parquet")
.filter(pl.col("region") == "华东")
.select(["category", "sales"])
)
# 优化后的计划:可以看到 PROJECT 2/100 columns、SELECTION 已下推
print(lf.explain())
# 看未优化的原始计划做对比
print(lf.explain(optimized=False))
# 图形化(需 graphviz)
lf.show_graph()
怎么读 explain
输出里 PROJECT 2/100 columns 表示投影下推生效,只读 2 列;SELECTION: [col("region") == "华东"] 出现在 Parquet SCAN 节点里,说明谓词已下推到读取层。看到这些就说明优化器在替你省钱。
7.4 collect 与 streaming
# 普通 collect(数据能装进内存)
df = lf.collect()
# 流式执行:处理大于内存的数据
df = lf.collect(engine="streaming")
# 结果太大,别落回内存 → 直接流式写盘
lf.sink_parquet("out.parquet")
lf.sink_csv("out.csv")
sink_ 的威力
sink_parquet 把整条惰性管道边算边写盘,全程内存占用极低。做"读 TB 级原始数据 → 清洗 → 落地"的 ETL,这是首选,不必 collect 到内存。
7.5 Eager 与 Lazy 互转
# Eager → Lazy
lf = df.lazy()
# Lazy → Eager(执行)
df = lf.collect()
# 只想看前几行验证管道对不对,用 fetch/head 省时
df.lazy().filter(...).head(5).collect()
7.6 常见误区
不要在 Lazy 管道里反复 collect
每 collect 一次就物化并中断优化。把整条链一路写到底、最后只 collect 一次,优化器才能看到全貌做下推。中途 collect 等于把优化拦腰截断。
map_elements 破坏优化
Python 逐行回调 map_elements 是黑盒,优化器无法下推、无法并行,还会拖慢十几倍。能用原生表达式(when/then、str/dt 命名空间)就绝不用它。
7.7 小结与下一步
- Lazy = 先建查询计划,
collect时优化器统一优化再执行。 - 三大优化:谓词下推、投影下推、公共子表达式消除。
- 大文件用
scan_*,大结果用sink_*,超内存用 streaming。 - 用
explain验证优化是否生效;别中途 collect,别用 map_elements。 - 下一章处理 时间序列。