订阅更新

Spark SQL的执行计划看不懂——Catalyst优化器到底做了什么?

Spark手册 第 8 题 约 3 分钟读完

本文目录
  1. 先说结论
  2. 用一个SQL例子走一遍四个阶段
  3. 怎么看执行计划?
  4. 实战经验:谓词下推失败的坑
  5. 总结

先说结论

Catalyst的优化分四个阶段:解析 → 逻辑优化 → 物理计划 → 代码生成。每个阶段都在帮你“做你懒得做或想不到的优化”。

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

详细讲解

Catalyst是Spark SQL的“大脑”,也是DataFrame比RDD快的核心原因。很多人知道Catalyst的存在,但说不清楚它具体做了什么。今天用一个真实的SQL例子,把Catalyst的四个优化阶段讲清楚。

用一个SQL例子走一遍四个阶段

SELECT u.country, SUM(o.amount) AS total
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.dt = '2025-03-15' AND o.status = 'paid'
GROUP BY u.country;

阶段一:解析(Analysis)

把SQL文本解析成抽象语法树(AST),然后通过Catalog解析表名、列名、函数。比如确认orders表存在、amount列是数字类型等。如果写错了列名,在这个阶段就会报错。

阶段二:逻辑优化(Logical Optimization)

这是Catalyst最重要的阶段,包括多种优化规则:

谓词下推(Predicate Pushdown): 把WHERE o.dt = '2025-03-15'推到数据源层面,读Parquet时直接跳过其他分区,大幅减少IO。

列裁剪(Column Pruning): 只读取需要的列(user_id, amount, dt, status),不读其他列。对于列很多的宽表,效果特别明显。

常量折叠: 如果你写了WHERE x > 1 + 1,Catalyst会在编译时就算好变成WHERE x > 2。

Join重排序: 如果你写的JOIN顺序不是最优的,Catalyst会自动调整。比如先过滤再JOIN,而不是先JOIN再过滤。

阶段三:物理计划(Physical Planning)

把逻辑计划转换为可执行的物理计划。比如决定用Broadcast Join还是Sort Merge Join——根据表的统计信息(行数、大小)自动选择最优的物理算子。

阶段四:代码生成(Whole-Stage Code Generation)

把整个Stage的操作编译成一个Java方法,避免了每个算子之间的虚函数调用开销。这就是Tungsten的核心技术,性能提升可以达到10倍以上。

怎么看执行计划?

-- 简单版:看物理计划
EXPLAIN SELECT ...;
 
-- 详细版:看所有阶段的计划
EXPLAIN FORMATTED SELECT ...;
 
-- 在计划里看到的关键信息:
-- PushedFilters: [IsNotNull(dt), EqualTo(dt,2025-03-15)]  ← 谓词下推成功
-- ReadSchema: struct<user_id:bigint,amount:decimal>       ← 列裁剪成功
-- BroadcastHashJoin                                       ← 选择了广播Join

实战经验:谓词下推失败的坑

我在出海硬件公司遇到过一个场景:有人写了一个UDF做日期处理,把WHERE条件包在了UDF里面。结果Catalyst无法穿透UDF做谓词下推,数据源读了全量数据。解决方案:把过滤逻辑拆到UDF外面,先过滤再进入UDF。

教训:UDF会打断Catalyst的优化链。能用Spark内置函数就不用UDF。

总结

Catalyst四个阶段:解析→逻辑优化→物理计划→代码生成。核心优化:谓词下推、列裁剪、常量折叠、Join重排序。用EXPLAIN查看执行计划验证优化是否生效。注意UDF会打断优化。面试时能用一个SQL例子走一遍四个阶段,就很有深度。

下一篇聊一个很多人只知其然不知其所以然的问题:同样的SQL在Hive跑10分钟在Spark跑2分钟,Spark到底快在哪?

你们生产环境遇到过UDF打断优化的问题吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

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