Spark SQL的执行计划看不懂——Catalyst优化器到底做了什么?
先说结论
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年大数据老兵,聊技术、聊职场、聊成长