注册
HAGR2 哈希分组聚集的等价改写
专栏/技术分享/ 文章详情 /

HAGR2 哈希分组聚集的等价改写

Gyyyyy 2026/10/23 52 1 0
摘要

达梦 SQL 优化实战:HAGR2 哈希分组聚集的等价改写,从 56 秒到毫秒级

导读:生产环境的一条 SQL,只返回 16 行记录,却要跑 56 秒——问题不在数据量,而在一个叫 HAGR2(HASH 分组聚集) 的操作符上。本文还原一个真实生产案例的完整优化过程:如何用 trace 定位到慢操作符、如何读懂执行计划里的分组聚集、以及如何用「EXISTS 半连接 + 标量子查询」等价改写,把这条 SQL 从 56 秒打到毫秒级。全程附可复现的 SQL 与实测数据。


一、问题背景:16 行结果,56 秒执行

在太平费控系统现场,有一条 SQL 执行后只返回 16 行记录,却耗时 56 秒。这种「结果集极小但耗时巨大」的反差,几乎可以直接断定:瓶颈不在返回数据的多少,而在中间过程的重复计算。

这类问题的排查入口不是猜,而是把 SQL 拆开看执行码。达梦提供了 trace 机制,可以精确统计每条 SQL 内部每个操作符的耗时占比。

二、定位:用 trace 找到「吃掉 53 秒」的操作符

2.1 开启监控

先确认监控相关参数,并在会话级开启执行监控:

-- 查看 monitor 相关参数 SELECT * FROM v$dm_ini WHERE PARA_NAME LIKE '%MONITOR%'; -- 会话级开启 SQL 执行监控 CALL SF_SET_SESSION_PARA_VALUE('MONITOR_SQL_EXEC', 1); CALL SF_SET_SESSION_PARA_VALUE('ENABLE_MONITOR_DMSQL', 1);

2.2 看执行码,定位慢点

在管理工具中对原 SQL 做 trace,查看执行码中每个操作符的耗时。结果非常清晰:

操作符 名称 耗时
HAGR2 HASH 分组聚集 约 53 秒
其余(连接、投影等) — 约 3 秒

结论:这条 SQL 慢就慢在 HAGR2 操作符上,它单独吃掉了 53 秒。HAGR 即 HASH 分组聚集(HASH GROUP BY),是执行 GROUP BY 时的典型算子。

2.3 定位到对应 SQL 片段

沿着执行计划里 71 行对应的 HAGR2 节点,回到 SQL 文本去看具体逻辑。慢的部分是这样的结构:

-- 内联视图:先对 w 表按 document_number 分组,再取每个分组的 max(instance_id) SELECT h.id, h.apply_number, wwi.instance_id FROM h_apply h INNER JOIN ( SELECT MAX(w.instance_id) AS instance_id, w.document_number FROM wfl_workflow_instance w WHERE w.status IN ('1', '10') GROUP BY w.document_number -- ← 这里触发 HAGR2 ) wwi ON h.apply_number = wwi.document_number;

三、根因:GROUP BY + MAX 是「先全量算,再拿去匹配」

问题的本质在于执行语义的差别:

  • GROUP BY + MAX:先把 wfl_workflow_instance 表全量扫描、全量分组聚合,算出每个 document_number 的 max(instance_id),物化出一张中间结果,再与 h_apply 做连接。相当于「先把整个账本算完,再按需取用」。
  • EXISTS + 标量子查询:不预聚合,而是对 h_apply 的每一行,按需去 wfl_workflow_instance 里探测,命中即止。

当 wfl_workflow_instance 表很大、而 h_apply 驱动行相对有限时,前者的全量聚合成本会被无限放大——这正是 53 秒的来源。

四、等价改写:EXISTS 半连接 + 标量子查询

改写思路分两步,把「聚合」拆成「存在性判断」和「按需取值」:

SELECT h.id, -- 标量子查询:按需取每个单据号的最大 instance_id (SELECT MAX(w.instance_id) FROM wfl_workflow_instance w WHERE w.status IN ('1', '10') AND h.apply_number = w.document_number ) AS instance_id FROM h_apply h WHERE EXISTS ( -- 半连接:只判断"该单据是否存在符合条件的实例" SELECT 1 FROM wfl_workflow_instance w WHERE w.document_number = h.apply_number AND w.status IN ('1', '10') );

改写后重新执行,执行时长直接降到毫秒级,wfl_workflow_instance 表走 document_number 上的二级索引。

五、执行计划对比:全表扫 → 索引探测

5.1 改写前(慢):全表扫描 + 全量聚合 + 大表 HASH JOIN

#NSET2: [89989462, ...]
  #PRJT2
    #HAGR2: [89989462, ...]; grp_num(4), sfun_num(1)      -- 全量 HASH 分组聚合
      #HASH2 INNER JOIN  KEY(W.DOCUMENT_NUMBER = H.APPLY_NUMBER)
        #CSCN2: [631, 5000000, ...]  w 表全表扫描 500 万行  ← 慢点①
        #CSCN2: [237, 2000000, ...]  h 表全表扫描 200 万行  ← 慢点②

三个慢点一目了然:

  1. w 表 CSCN2 全表扫描 500 万行;
  2. h 表 CSCN2 全表扫描 200 万行;
  3. 两者 HASH JOIN 后还要 HAGR2 全量分组聚合,估算代价高达 89989462(近 9000 万)。

5.2 改写后(快):索引探测 + 半连接命中即止

#NSET2: [1194, 5000, ...]
  #PIPE2
    #PRJT2
      #HASH2 INNER JOIN
        #NEST LOOP INDEX JOIN2                    -- h 表驱动,w 表逐行索引探测
          #CSCN2: [237, 2000000, ...] h 表
          #BLKUP2 / #SSEK2: 索引探测
      #SPL2 / #AAGR2                             -- 标量子查询逐行取 MAX
        #HASH RIGHT SEMI JOIN2
          #BLKUP2 / #SSEK2: w 走 document_number 二级索引

改写后:

  1. w 表不再全表扫,而是通过 document_number 索引 SSEK2 索引范围探测;
  2. EXISTS 半连接命中即止,无需对全部实例做聚合;
  3. max(instance_id) 改为标量子查询逐行聚合,避免一次性物化海量中间结果;
  4. 总代价从 89989462 降到 1194,量级差异约 7.5 万倍。

六、模拟复现:把「秒级 → 毫秒级」跑给你看

为了固化这个优化手法,在测试环境搭建了同型场景(wjh-test-db3,端口 5636):

对象 规模
wfl_workflow_instance 500 万行,document_number 建二级索引
h_apply 200 万行

三种 SQL 形态的实测耗时:

形态 SQL 结构 执行时间 相对提速
A(原版) 内联视图 GROUP BY 聚合 + JOIN 15.90 s 1×
B(改写1) EXISTS 半连接 865 ms ≈ 18×
C(改写2) EXISTS + 标量子查询取 MAX 739 ms ≈ 21×

三者结果集一致(1,200,000 行),改写前后语义等价 ✓。生产案例原 56 秒 → 毫秒级,量级与本模拟一致(秒级 → 毫秒级)。

七、为什么等价

校验点 说明
过滤条件一致 原 WHERE status IN ('1','10') 与改写后的过滤条件完全相同
存在性判断一致 INNER JOIN 本身有「匹配才保留」的语义,EXISTS 半连接完全承接
取最大值一致 原 GROUP BY document_number 取 MAX(instance_id),改写后用标量子查询逐行取 MAX,结果一致
走对索引 wfl_workflow_instance.document_number 上有二级索引,改写后才能走 SSEK2 探测

补充:如果业务只需要「存在性判断」、不需要携带 max(instance_id),用 B 形态即可;若需携带聚合值,用 C 形态(EXISTS + 标量子查询)。

八、总结

  1. 慢 SQL 别急着加索引,先看执行计划定位贵节点。本案例用 trace 锁定 HAGR2 占了 53 秒,才有的放矢。
  2. GROUP BY + MAX 与 EXISTS + 标量子查询 是两种完全不同的执行哲学:前者「先全量算再匹配」,后者「按需探测」。驱动行相对有限、被驱动表很大时,后者优势呈数量级。
  3. 执行计划是最好的诊断书:CSCN2(全表扫)、HAGR2(哈希分组聚集)、SSEK2(二级索引扫描)、SEMI JOIN(半连接)这几个操作符,把慢的机理讲得清清楚楚。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服