Spark SQL 慢 SQL 排查实战:从 explain 到 Stage 指标一步步定位

长文技术整理分类:Spark阅读 5820评论 482026-04-26
围绕 Spark 官方 AQE、执行计划和 Spark UI 指标,整理慢 SQL 的真实定位方法:先看扫描与 Join,再看 Shuffle 和拖尾。

为什么慢 SQL 不能只靠加资源

Spark SQL 变慢时,很多人第一反应是加 executor、调大内存、提高并行度。这些动作有时能让任务暂时跑过去,但很难解释为什么昨天 12 分钟、今天 58 分钟。慢 SQL 的核心不是“资源够不够”,而是执行计划和数据分布有没有变化。

一条 SQL 的耗时通常由四部分组成:扫描数据、Join、Shuffle、聚合/排序。扫描变大说明分区裁剪或文件裁剪失败;Join 变慢可能是小表没有广播或 Join Key 类型变化;Shuffle 变大说明中间结果膨胀;最后少数 Task 拖尾,多半是数据倾斜。

执行计划怎么看

建议用 EXPLAIN FORMATTED,不要只看简单 explain。formatted 会把逻辑计划、优化后计划、物理计划拆开,能看到 FileScan、PartitionFilters、PushedFilters、BroadcastExchange、SortMergeJoin、HashAggregate、Exchange 等节点。

EXPLAIN FORMATTED
SELECT /* ads_user_retention */
       a.dt,
       b.user_level,
       count(distinct a.user_id) AS uv
FROM dwd_event a
LEFT JOIN dim_user b
  ON cast(a.user_id as bigint) = b.user_id
WHERE a.dt = '${bizdate}'
  AND a.event_name = 'pay_success'
GROUP BY a.dt, b.user_level;

如果 FileScan 节点里没有 PartitionFilters,说明分区裁剪没生效。常见原因是分区字段被函数包住、字段类型不一致、SQL 漏了 dt 条件,或者视图里把分区字段改名后下游忘记传递。

Join 策略变化是高频根因

小维表本来应该 BroadcastHashJoin,结果变成 SortMergeJoin,性能会明显下降。常见原因有三个:维表统计信息过期、维表文件过多导致估算偏大、Join Key 两边类型不同导致优化器判断不准。

ANALYZE TABLE dim_user COMPUTE STATISTICS;
ANALYZE TABLE dim_user COMPUTE STATISTICS FOR COLUMNS user_id;

-- 临时验证,不建议长期无脑 hint
SELECT /*+ BROADCAST(dim_user) */ ...

如果加 broadcast hint 后性能恢复,就要回头治理维表大小、统计信息和字段类型,而不是把 hint 当永久方案。

AQE 的几个关键配置

AQE 会在运行时根据真实 shuffle 数据量调整计划。它可以合并小分区、处理部分 skew join、动态调整 Join 策略,但前提是 SQL 本身不要太差。

set spark.sql.adaptive.enabled=true;
set spark.sql.adaptive.coalescePartitions.enabled=true;
set spark.sql.adaptive.skewJoin.enabled=true;
set spark.sql.adaptive.advisoryPartitionSizeInBytes=134217728;
set spark.sql.shuffle.partitions=400;

shuffle.partitions 不要固定照搬 200。小任务 200 可能太多,大任务 200 又可能太少。更稳的方式是结合输入规模和目标单分区大小估算,再交给 AQE 合并。

数据倾斜怎么确认

不要凭感觉说倾斜。Spark UI 的 Stage 页面里,如果 Task Duration、Shuffle Read、Input Size 分布极不均匀,并且最长 Task 远大于中位数,再查 Key 分布。

SELECT user_id, count(*) cnt
FROM dwd_event
WHERE dt='${bizdate}'
GROUP BY user_id
ORDER BY cnt DESC
LIMIT 50;

很多热点来自脏值:null、unknown、0、-1、空字符串。这类 Key 不应该直接进入主链路 Join 或聚合,可以提前过滤、单独统计,或者分流到异常数据表。

一套实用排查顺序

  1. 看任务是否资源排队,排除集群层问题。
  2. 看 explain,确认扫描、Join、Exchange、Aggregate 是否异常。
  3. 看 Spark UI,定位慢 Stage 和拖尾 Task。
  4. 查分区、统计信息、文件数量、热点 Key。
  5. 先改 SQL 和表,再调参数,最后才扩资源。

延伸阅读和落地建议

本文按公开技术资料和生产经验重新整理,没有复制原文。真正落地到你的环境时,建议补充:集群版本、资源规格、任务截图、SQL 执行计划、核心监控曲线、上线前后对比数据。这样文章会更像真实生产复盘,也更适合长期维护。