Hive 与 Trino 慢查询:执行计划分析与分层治理
用统一的计划解读框架分析 Hive EXPLAIN、Trino EXPLAIN ANALYZE 与 Doris Profile,定位慢查询根因
慢查询的第一步不是调参,而是拿执行计划定位瓶颈。结论:Hive 用 `EXPLAIN` 与 `EXPLAIN EXTENDED` 看逻辑与物理计划,Trino 用 `EXPLAIN ANALYZE` 拿算子级实际行数与耗时,Doris 用 `EXPLAIN` 与执行 Profile 对照估算值与实际值;三者的共同读法是先看扫描是否全表、再看 join 是否倾斜、最后看统计信息是否失效,然后按"统计信息 → SQL 改写 → 引擎参数 → 数据模型"四层依次治理。
症状识别:先分清是哪一类慢
- 全表扫描型:计划里扫描的分区数远超过滤条件应有的数量,或扫描行数与估算行数严重不符。典型原因是分区列上套了函数、类型不匹配导致谓词无法下推、或分区裁剪失败。
- join 倾斜型:单个 reducer 或 task 的耗时远高于同阶段其他任务,EXPLAIN ANALYZE 中同一算子的各实例行数差异极大。典型原因是 join 键分布不均或存在热点键。
- 统计信息失效型:计划中的估算行数与实际行数相差数个数量级,导致优化器选错 join 顺序、join 方式或聚合策略。
- 资源排队型:SQL 本身不慢,但排队时间长。Hive 的计划里看不出排队,要结合队列等待时间与集群负载判断,避免误调执行参数;Trino 的
EXPLAIN ANALYZE会把 Queued 时间单列,可直接区分排队与执行。
计划解读:三个引擎怎么看
Hive EXPLAIN。EXPLAIN 输出逻辑计划与物理计划,EXPLAIN EXTENDED 额外给出统计信息、分区过滤下推情况与各 MapReduce 或 Tez 阶段的表别名。重点看四处:TableScan 上是否带分区过滤条件;join 的左右表顺序与 join 类型(Map Join 还是 Reduce Join);Reduce 阶段的数量与是否触发倾斜处理;输出中是否出现带行数的统计信息条目。统计信息缺失时,基于 Calcite 的代价优化只能用默认估算值,计划质量会明显下降。
Trino EXPLAIN ANALYZE。EXPLAIN (TYPE DISTRIBUTED) 给出分布式执行计划,EXPLAIN ANALYZE 会真实执行查询并返回每个算子的估算行数与实际行数、wall time 与处理速率。重点看:TableScan 的估算行数与实际行数偏差;join 算子标注的分布类型(broadcast 还是 repartition);各 stage 的耗时分布。偏差超过一个数量级时,先补统计信息再谈其他优化。
读 EXPLAIN ANALYZE 输出先看头部的分段计时:官方文档示例中依次列出 Queued、Analysis、Planning 与 Execution 四段时间,Queued 占比高说明瓶颈在队列与资源而非 SQL 本身。往下每个 Fragment 与算子都带 CPU、Scheduled 与 Blocked 时间,Blocked 占比高指向内存或数据交换等待,而不是算子本身慢。倾斜有直接指标:官方示例中算子的 Input avg. 与 Input std.dev. 成对出现,同阶段各实例输入行数的标准差达到数百个百分点即为倾斜。扫描算子上还会输出 Input: N rows, Filtered: X%, Physical Input: Y MB,Filtered 比例过低说明谓词没有有效裁剪数据,是全表扫描型问题的直接证据。加 VERBOSE 选项可拿到算子级分布指标(如 CPU 时间分布、输入行数分布的分位数),用于判断是长尾任务拖慢整体还是整体性偏慢。一个反直觉的注意点:官方文档明确提示,执行很快的查询其统计未必准确,不要用秒级完成的查询下结论。Trino 的统计信息来自 connector,可用 ANALYZE table 主动收集,用 SHOW STATS FOR table 确认是否已收集。
Doris EXPLAIN 与 Profile。EXPLAIN 显示扫描节点的估算行数与分区、分桶裁剪结果;set enable_profile = true 之后可拿到执行 Profile,对照算子级实际耗时。读法与 Trino 一致:先核对估算与实际的偏差,再定位耗时集中的算子。
大数据基础平台 EasyMR
统一存储、计算与运维,支撑批、流、OLAP 与湖仓一体的国产化大数据底座平台
根因判定顺序
- 对比估算行数与实际行数,偏差大则优先判定统计信息问题,先
ANALYZE再重看计划。 - 计划中无分区过滤,或过滤条件未下推到扫描节点,判定为全表扫描,改 SQL 写法。
- join 算子各实例耗时或行数差异大,判定为数据倾斜,检查 join 键分布与热点值。
- 执行计划合理但整体仍慢,再看集群负载、并发与资源排队。
判定阈值建议作为团队约定固化:估算与实际偏差在两倍以内视为正常估算波动;偏差达到一个数量级及以上,判定统计信息问题,先补统计再看计划;偏差极大但扫描行数正常时,转查 join 键分布。治理要闭环验证:Hive 侧重跑 EXPLAIN 对比收集前后的 join 顺序与扫描行数估算;Trino 侧重用 SHOW STATS FOR 确认统计存在,再用 EXPLAIN ANALYZE 对比实际行数。只执行 ANALYZE 不回看计划,等于没有验证。
分层治理动作
第一层:统计信息治理。Hive 执行 ANALYZE TABLE ... COMPUTE STATISTICS,并用 FOR COLUMNS 收集列级统计;确认自动收集开关 hive.stats.autogather 处于开启状态。Trino 对大表执行 ANALYZE table,确认 connector 是否自动更新统计。Doris 用 ANALYZE TABLE 收集统计。列级统计(尤其是 join 键与过滤列)对 join 顺序选择影响最大。Hive 侧还要注意分区级与列级统计是两步:只跑 COMPUTE STATISTICS 拿到的是分区级统计,join 顺序与谓词选择依赖的列级统计需要显式用 FOR COLUMNS 收集。
第二层:SQL 改写。分区过滤条件直接写在分区列上,避免对分区列套函数;join 键两侧类型保持一致,避免隐式转换;避免 SELECT * 带出大字段;对热点 join 键做打散处理(如拼接随机后缀);大表 join 优先改为按分区裁剪后再 join。
第三层:引擎参数调优。Hive 侧调整 map join 阈值(hive.auto.convert.join、hive.mapjoin.smalltable.filesize)、reducer 数量与倾斜 join 开关(hive.optimize.skewjoin)。Trino 侧调整 join 分布类型、task 并发与内存配置。参数调整应在统计信息与 SQL 已正确的前提下进行,否则只是在掩盖问题。
第四层:数据模型改造。分区与分桶键应与高频过滤、join 条件对齐;高频固定报表走预聚合或物化视图,把长尾明细查询与高频聚合查询分离;小文件合并与数据倾斜源头治理(如按业务键重新分桶)。
四层动作有严格的先后关系:统计信息没收对就调参数,等于让优化器基于错误的输入做"正确"的选择;SQL 改写没做就动数据模型,改造成本会成倍放大。每完成一层都要重跑一次 EXPLAIN ANALYZE 留存对比,确认瓶颈确实上移了,再进入下一层。
经营分析
围绕目标、预算、执行、异常与归因,构建经营分析、过程监控、问题定位与决策闭环。
常见误读与规避
- 把估算行数当实际行数:估算只服务优化器选型,不对外承诺结果;凡是要下结论的数字,都以
EXPLAIN ANALYZE的实际输出为准。 - 只看单个 stage 的耗时:整体慢可能是上游 stage 持续喂数据、下游 stage 长时间处于 Blocked,要按数据流方向看整条链路。
- 忽略 Blocked 时间:Blocked 高而 CPU 低时,加并发或调并行度往往无效,应先查内存压力与数据交换。
- 用快速完成的查询下统计结论:Trino 官方文档已提示这类统计未必准确,慢查询分析应基于真正慢的那一次执行的计划。
收尾检查清单
- 慢查询是否已用 EXPLAIN ANALYZE 留存优化前后的计划对比
- 相关大表的统计信息最近一次收集时间是否在可接受范围内
- 分区过滤条件是否写在分区列上且无函数包裹
- join 键两侧类型是否一致、是否存在热点键
- 预聚合是否覆盖了高频查询,长尾查询是否有明细层兜底
- 参数调整是否基于正确的计划,而非绕过计划问题


