SQL跑得慢?先看执行计划再说
总结
| 看什么 | 怎么看 | 优化方向 |
|---|---|---|
| ------- | -------- | --------- |
| 分区裁剪 | PartitionFilters非空 | 避免对分区键做函数运算 |
| 谓词下推 | PushedFilters有值 | 提前过滤减少数据量 |
| JOIN策略 | BroadcastHashJoin vs SortMergeJoin | 小表走Broadcast |
| Shuffle次数 | Exchange节点数量 | 减少不必要的Shuffle |
| Stage数量 | 越少越好 | 合并可合并的操作 |
养成习惯:写完SQL先跑一次EXPLAIN,确认执行计划符合预期,再提交任务。 这个习惯能帮你避开80%的性能问题。我们组规定提交到生产的SQL必须附上EXPLAIN截图,这条规则推行之后,线上慢查询减少了一半以上。
明天 DAY 18 聊窗口函数的PARTITION BY vs GROUP BY,很多人搞不清楚这两个分组的区别,今天一次讲透。
你在工作中用EXPLAIN发现过什么让你惊讶的事情?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
面试官问"你怎么优化SQL",最忌讳的回答是"加个索引""用MapJoin"这种直接开药方的。正确的思路应该是:先用EXPLAIN看执行计划,找到瓶颈,再针对性优化。 执行计划就是SQL的X光片,今天教你怎么读。
为什么要看执行计划?
你写的SQL和数据库实际执行的SQL可能完全不同。优化器会对你的SQL做改写:调整JOIN顺序、下推过滤条件、选择JOIN策略(Broadcast or Shuffle)、决定是否用索引等。
如果你不看执行计划,就不知道优化器做了什么决策,也无法判断优化是否生效。
Hive的EXPLAIN
EXPLAIN
SELECT u.user_name, o.order_amount
FROM user_order o
JOIN dim_user u ON o.user_id = u.user_id
WHERE o.dt = '2025-01-01';
Hive的EXPLAIN输出是一棵Stage树,每个Stage对应一个MapReduce Job。关键看这几个信息:
1. Stage数量: Stage越多意味着越多次Shuffle。如果一个简单查询出现了3个以上的Stage,可能有优化空间。
2. Map Operator Tree: 看有没有做分区裁剪(filterExpr里出现分区字段)和谓词下推。
TableScan
alias: o
filterExpr: (dt = '2025-01-01') ← 分区裁剪生效
Filter Operator
predicate: (user_id is not null) ← 谓词下推
3. Join Operator: 看JOIN类型是Map Join还是Common Join。Map Join Operator说明小表走了广播,性能好。如果看到Reduce Output Operator紧跟JOIN,说明走的是Shuffle Join。
Spark SQL的EXPLAIN
Spark的执行计划更详细,关键节点:
1. Scan节点——数据读取
FileScan orc user_order[user_id, order_amount, dt]
PartitionFilters: [dt = '2025-01-01'] ← 分区裁剪
PushedFilters: [IsNotNull(user_id)] ← 谓词下推
PartitionFilters有值说明分区裁剪生效了。
2. Join节点——JOIN策略
BroadcastHashJoin [user_id], [user_id], Inner ← 最快
SortMergeJoin [user_id], [user_id], Inner ← 涉及Shuffle
如果你期望走Broadcast但实际走了SortMerge,说明小表大小超过了阈值,需要调参数或加Hint。
3. Exchange节点——Shuffle
Exchange hashpartitioning(user_id, 200) ← 一次Shuffle
Exchange越少,性能越好。
实战:用执行计划诊断问题
案例一:分区裁剪没生效
我有一次写了这样的SQL:
SELECT * FROM user_event
WHERE DATE_FORMAT(dt, 'yyyy-MM') = '2025-01'
跑了40分钟,平时同样数据量只要2分钟。看执行计划发现PartitionFilters是空的——对分区键做了函数运算,裁剪失效了。改成WHERE dt BETWEEN '2025-01-01' AND '2025-01-31'之后,2分钟跑完。
案例二:该走Broadcast没走
dim_region只有200行,但走了SortMergeJoin。原因是Spark没有收集统计信息,不知道它有多小。
-- 手动指定Broadcast
SELECT /*+ BROADCAST(r) */ *
FROM fact_table f JOIN dim_region r ON f.region_id = r.region_id
-- 或刷新统计信息
ANALYZE TABLE dim_region COMPUTE STATISTICS;
案例三:发现数据倾斜
执行计划本身不会直接告诉你倾斜,但可以结合Spark UI看。先从执行计划找到Shuffle Exchange对应的Stage ID,然后在Spark UI看Task分布——某个Task数据量是其他的10倍以上,就是倾斜。
我排查倾斜的流程:EXPLAIN → 找到Shuffle Stage → Spark UI看Task分布 → 确认倾斜Key → 加盐打散。
面试高频问题
Q:CBO和RBO有什么区别?
RBO(基于规则): 按固定规则改写SQL,不考虑数据量
CBO(基于代价): 收集统计信息,估算每种计划的代价,选最优的
Spark的CBO需要ANALYZE TABLE收集统计信息。没有统计信息,CBO退化成RBO。
-- 收集统计信息
ANALYZE TABLE user_order COMPUTE STATISTICS;
ANALYZE TABLE user_order COMPUTE STATISTICS FOR COLUMNS user_id, order_amount;
Q:AQE是什么?
AQE(Adaptive Query Execution)是Spark 3.0引入的运行时优化。和CBO在编译期优化不同,AQE在运行中根据实际数据动态调整:自动切换JOIN策略、自动合并小分区、自动处理倾斜。
SET spark.sql.adaptive.enabled=true;
AQE是Spark最重要的性能提升之一。面试能讲清楚CBO、AQE的关系和区别,说明你对优化器理解很深。