订阅更新

SQL跑得慢?先看执行计划再说

SQL手册 第 17 题 约 6 分钟读完

本文目录
  1. 总结
  2. 为什么要看执行计划?
  3. Hive的EXPLAIN
  4. Spark SQL的EXPLAIN
  5. 实战:用执行计划诊断问题
  6. 面试高频问题

总结

看什么怎么看优化方向
------------------------
分区裁剪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的关系和区别,说明你对优化器理解很深。

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