跳过正文

达梦 DM8 SQL 执行计划与操作符实战:从 EXPLAIN 到 AUTOTRACE、ET

Greatfinish
作者
Greatfinish
记录 Oracle、PostgreSQL、达梦、Linux、存储与生产环境故障处理经验。
目录

适用范围:达梦数据库 DM8。本文基于 DM8 单机版实测,核心分析方法同样适用于并行、MPP 和 DPC 环境;具体操作符、字段和参数以目标实例版本为准。

实测环境:DM Database Server 64 V8,构建号 03134284604-20260707-335949-20228,ARM64 Linux。实测日期:2026-08-18;结构修订日期:2026-08-19。

测量口径:下文性能值来自同一测试环境中的单次实测;所列主要样本的物理读均为 0,因此不能外推为冷读性能。数据主要用于解释计划机制和相对差异,不代表其他硬件、数据分布或并发条件下的绝对性能。各实战对比表中的语句级执行时间统一采用 AUTOTRACE Statisticsexec time(ms),不使用 DIsql 页脚的“已用时间”。

SQL 调优的可靠路径不是在计划里搜索“全表扫描”或“HASH JOIN”,然后机械地建索引、改 Hint。真正可靠的方法是:分析执行计划,先看数据库是怎么取数据的,再看预估行数和实际行数差多少,最后用逻辑读、执行时间、执行次数以及内存、磁盘使用情况来验证优化效果。

本文按“模型与方法 → 操作符 → 工具 → 环境 → 三组实战 → 排查总结”组织:

  1. 执行计划基础与阅读方法
  2. 常用操作符二维速查与性能判断
  3. EXPLAIN、AUTOTRACE 与 ET
  4. 构建测试环境
  5. 实战:扫描、索引、回表与覆盖索引
  6. 实战:聚合与连接算法
  7. 实战:排序、窗口函数与集合运算
  8. 总结:排查流程与常见误区
  9. 参考资料

1. 执行计划基础与阅读方法
#

1.1 执行计划是优化器选择的执行方式
#

同一条 SQL 往往存在多种访问路径、连接顺序和连接算法。DM 优化器根据对象统计信息、谓词选择率、索引、系统资源估值和 Hint 等因素,为候选方案估算代价并选择执行计划。

执行计划表达的是“数据库准备怎样取得结果”,不是 SQL 文本的逐句翻译。优化器可能:

  • EXISTSIN 转换为半连接,把 NOT EXISTS 转换为反半连接;
  • UNION 实现为 UNION ALL 加去重;
  • 利用索引已有顺序省略 SORT3
  • ACTRL 在运行时选择主计划或备用路径;
  • 在并行、MPP 或 DPC 环境插入收集、广播、重分发和发送/接收节点。

具体优化机制见官方查询优化文档

1.2 执行计划要从下往上看
#

先看一个简单的执行计划:

1   #NSET2: [11, 10000, 150]
2     #PRJT2: [11, 10000, 150]
3       #BLKUP2: [11, 10000, 150]; IDX_DPO_STATUS(DMPLAN_ORDER)
4         #SSEK2: [11, 10000, 150]; scan_range['REFUND','REFUND']

执行计划可以理解成一棵倒过来的树:

  • 越靠上的节点层级越高;
  • 缩进越深,说明它是上层节点的子节点;
  • 真正取数据的动作通常发生在底层;
  • 数据取出来以后,再一层层往上传;
  • 最上面的 NSET2 一般就是最终返回结果的位置。

因此,平时看执行计划时,可以先从最下面的节点开始看,再逐层往上分析。

比如上面的计划,大致可以理解成:

SSEK2
  
BLKUP2
  
PRJT2
  
NSET2

也就是先通过 SSEK2 从索引中找到满足条件的数据,再通过 BLKUP2 回表取需要的列,然后经过 PRJT2 整理返回列,最后由 NSET2 输出结果。

不过要注意,实际执行并不是简单地把 4 → 3 → 2 → 1 各执行一次。

像嵌套循环连接这类操作,一个子节点可能会被反复调用很多次。因此分析实际执行情况时,还要结合 N_ENTER,看看某个操作符到底被进入了多少次。

1.3 三元组表示估算值,不是执行时间
#

达梦执行计划中的操作符后面,经常会看到这样的三个数字:

#CSCN2: [29, 200000, 150]

可以先简单理解成:

位置表示什么不要误解成
第 1 项 29优化器估算的代价 COST不是执行了 29 毫秒
第 2 项 200000预计这个节点会处理或输出多少行不是最终 SQL 一定返回 20 万行
第 3 项 150预计每行数据的长度,单位是字节不是整个节点一共处理 150 字节

其中最容易误解的是 COST

COST 只是优化器用来比较不同执行方案的一个估算值,它综合考虑了 I/O、CPU、内存等因素。COST 小不等于执行时间一定短,COST 大也不等于 SQL 一定慢。

另外,父节点的 COST 通常已经包含了子节点的成本,所以不能把每一层的 COST 全部加起来。

开启实际执行统计以后,可能会看到:

[29, 200000->200000, 150]

这里:

200000 -> 200000
预计行数    实际行数

前面的 200000 是优化器事先估算的,后面的 200000 是 SQL 实际执行后得到的行数。

实际排查慢 SQL 时,这个“预计行数和实际行数差多少”往往比 COST 更值得关注。

例如:

[10, 100->100000, 80]

优化器原本认为这里只会有 100 行,实际却跑出了 10 万行,这种情况下,后面的连接顺序、连接方式、索引选择都可能跟着选错。

1.4 accessfilter 的区别
#

执行计划后面经常还能看到谓词信息,例如:

Predicate Information (identified by operation id):
---------------------------------------------------
3 - access(C.CUSTOMER_ID = O.CUSTOMER_ID)
4 - filter(DMPLAN_ORDER.STATUS = 'NEW')

可以简单理解成:

  • access数据库怎么找到数据
  • filter数据找出来以后,再把哪些记录过滤掉

比如:

access(C.CUSTOMER_ID = O.CUSTOMER_ID)

说明这个条件参与了数据访问或者表连接。

而:

filter(DMPLAN_ORDER.STATUS = 'NEW')

说明数据库已经拿到了一批数据,然后再检查 STATUS='NEW',不符合的记录再丢掉。

这两个位置的区别很重要。

假设一个条件本来只能匹配很少的数据,但它没有进入底层索引的 scan_range,而是到了上层才做 filter,就可能出现这种情况:

先读取 20 万行
        
再过滤
        
最后只留下 100 

这种计划通常就值得继续检查。

常见原因包括:

  • 索引列被函数包住了;
  • 条件两边数据类型不一致,发生了隐式转换;
  • 组合索引的前导列没有使用;
  • LIKE 的写法无法利用索引;
  • 统计信息不准确。

1.5 读一棵计划,按这五步检查
#

不要一看到全表扫描、回表或哈希连接就判断计划有问题。先从计划最下面的叶子节点开始,按数据向上流动的方向检查:

  1. 先看从哪里读数据。 确认访问的是哪张表、哪个索引,是扫描还是范围定位,scan_range 有没有真正缩小读取范围。
  2. 再看实际读了多少行。 比较每个节点的预计行数和实际行数,找到最早出现明显偏差的位置。
  3. 接着看行数在哪里变多、过滤在哪里发生。 如果下层读了很多行,到上层才过滤掉,说明过滤太晚;如果连接后行数突然变大,就检查连接条件和重复数据。
  4. 再找反复执行或占资源的节点。 N_ENTER 很大,说明节点被多次进入;出现 BLKUP2SORT3、哈希或临时结果时,再看它们处理了多少行、用了多少内存、是否落盘。
  5. 最后看整条 SQL 是否真的更快。 比较逻辑读、物理读和执行时间,同时确认结果没有变化。COST 下降或某个节点变快,都不能单独证明优化成功。

先用这五步找到问题位置,再到下一章查对应操作符的含义、风险和调优方向。

2. 常用操作符二维速查与性能判断
#

表里的操作符名称和基本含义,主要参考达梦官方文档V$SQL_NODE_NAME 为准。至于这个操作符有没有性能问题、应该怎么优化,不能只看名称,要结合实际处理行数、逻辑读、执行时间、内存和磁盘使用情况一起判断。

另外,操作符后面的 23 只是达梦内部不同版本或实现的标记,不代表数字越大性能就越好。

2.1 结果、访问与过滤
#

操作符/计划显示官方含义常见形态或关键字段性能关注点常见调优方向
NSET2收集结果集,通常位于根节点预计→实际行数、行长本身通常不是瓶颈;宽行会让整棵树搬运更多数据向下寻找首个行数膨胀或耗时高的节点;避免无必要的 SELECT *
PRJT2投影与表达式计算exp_numis_atom、输出行长复杂函数、重复表达式、输出列过多增加 CPU 和数据搬运只返回必要列;复用计算;必要时评估确定性函数索引
SLCT2条件过滤条件、slct_pushdown、过滤前后行数过滤过晚;函数、隐式转换导致条件不能形成索引范围改写为可索引表达式;统一数据类型;尽量下推高选择性条件
CSCN2聚集索引扫描idxname(tabname)btr_scanneed_slct大表只返回极少行时读取无关数据;但返回比例高时可能是最优看表规模、返回比例和逻辑读;高选择性条件再考虑索引
CSEK2聚集索引数据定位scan_typescan_range范围过宽仍会读取大量记录检查范围是否真正收窄;让索引列顺序匹配等值与范围条件
SSCN直接扫描整个二级索引索引名、btr_scanis_global不回表也可能扫完整个大索引确认是否在服务覆盖查询、排序或分组;高选择性条件争取转为 SSEK2
SSEK2二级索引按键值或范围定位scan_typescan_rangeis_global范围过宽,加回表后可能比顺序扫描更贵检查组合索引前导列与范围;结合 BLKUP2 评估总成本
BLKUP2根据二级索引记录定位基表记录索引名、use_clu_addr、输入行数大量离散回表造成随机读;嵌套循环中可能反复回表减少返回列;提高过滤性;对高频查询评估覆盖索引;少量回表是正常的
DSSEKDISTINCT 列上的索引跳跃扫描scan_typescan_range索引顺序或数据分布不匹配时无法使用让去重列与索引前导列匹配;与普通扫描加去重做实测比较
BMSEK/BMAND/BMOR/BMCVT/BMCNT/BMMG位图索引查找、与/或、ROWID 转换、计数和归并位图范围、组合方式、转换后行数高基数或频繁 DML 场景未必合适;候选行多仍会大量回表用于低基数、读多写少场景;关注组合后选择率和回表量
DSCN动态视图扫描动态视图名、过滤条件监控视图数据量和实时计算开销精确选择列与条件;避免高频无过滤轮询
ESCN外部表扫描外部数据源、过滤条件外部 I/O 和未下推过滤尽量下推过滤与投影;减少外部数据读取
REMOTE SCAN/RSCNDBLINK 远程表扫描table@dblink、远程 condition网络往返、未下推过滤、远端统计信息偏差把过滤、投影、聚合推到远端;大中间集可先受控落地本地
HFSCN/HFSCN2HUGE 表逐行扫描事务型/非事务型、表名大范围读取带来大量 I/O利用分区与过滤;只选必要列
HFSEK/HFSEK2HUGE 表按 KEY 查找scan_typescan_range范围过宽或 KEY 设计不匹配检查 KEY 与访问模式、范围裁剪
HFLKUP/HFLKUP2HUGE 表按 ROWID 回查输入行数大量 lookup 放大随机访问提高前置过滤性并减少回查列

2.2 聚合、排序、去重与分析
#

操作符/计划显示官方含义常见形态或关键字段性能关注点常见调优方向
AAGR2简单聚集;无分组时计算集函数sfun_numdistinct_flagCOUNT(DISTINCT ...) 仍可能消耗较大内存;下层可能扫描大量数据先过滤;检查 MIN/MAX 或 DISTINCT 参数能否利用索引
FAGR2快速聚集无过滤 COUNT(*),或基于索引的 MIN/MAX通常风险较低;增加条件或复杂表达式后可能无法触发保持语义正确,不要为了追求 FAGR 改变业务查询
HAGR2哈希分组并计算集函数grp_numkeysMEM_USEDDISK_USED高分组基数、倾斜或内存不足导致落盘提前过滤和缩窄行;收集分组列统计信息;比较有序输入方案
SAGR2对有序输入进行流式分组keys、下层有序来源为获得顺序而额外排序可能抵消收益利用以分组列为前导的索引;比较整个计划而非单一节点
SORT3排序,也可能承担去重或 Top-Nkey_numis_distincttop_flag、内存/磁盘大行数、宽行、排序区不足导致临时 I/O提前过滤投影;利用有序索引;下推 Top-N
DISTINCT/DIST删除重复行输入行数、去重列、下层顺序大结果去重消耗内存或落盘;可能掩盖错误的多对多连接先检查连接条件;不要求去重就移除;评估 DSSEK
TOPN2取得前 N 行,支持偏移和百分比top_numtop_offtop_percent深分页仍读取或排序大量前置行;无顺序时结果不稳定建立排序键索引;采用游标式分页;指定完整稳定排序键
RN生成或处理 ROWNUMTOPN2 的层级ROWNUM 与 ORDER BY 层级错误会改变语义明确先排序还是先编号
RNSKROWNUM 停止条件处理rownum_exp未形成停止键时仍可能读取大量行让上限条件尽早生效
AFUN分析/窗口函数计算partition_numorder_num大分区、宽行、多套窗口顺序带来排序与缓存压力先过滤、减少列;合并相同窗口;评估匹配顺序的索引

2.3 连接与半连接
#

操作符/计划显示官方含义适用形态主要风险调优方向
NEST LOOP INNER JOIN2嵌套循环内连接小驱动集、非等值连接或右侧可高效查找驱动集大时右侧被反复扫描,成本近似乘法增长让过滤后较小结果集驱动;校准基数;索引被驱动侧连接列
NEST LOOP INDEX JOIN2利用索引的嵌套内连接左侧每行驱动右侧 SSEK2/CSEK2外侧实际行数被低估时产生海量索引探测和回表控制驱动集;检查右侧 scan_rangeN_ENTER
NEST LOOP LEFT JOIN2 / NEST LOOP FULL JOIN2嵌套循环外连接需要保留外侧未匹配行大驱动集、条件位置限制谓词下推索引连接键;尽早过滤保留侧;保持 NULL 语义
NEST LOOP SEMI JOIN2嵌套半连接或反连接EXISTS/NOT EXISTS、非等值相关条件相关侧被反复执行尝试去相关化;索引关联列;看实际进入次数
INDEX JOIN LEFT JOIN2 / INDEX JOIN SEMI JOIN2索引方式的外连接、半连接或反连接被驱动侧连接键有有效索引外侧行数过多形成大量探测提高驱动侧选择性;减少回表
HASH2 INNER JOIN哈希内连接大结果集等值连接构建侧过大、键倾斜、内存不足和落盘连接前先过滤;减少行宽;更新统计信息;检查内存/磁盘和倾斜
HASH LEFT/RIGHT/FULL JOIN2哈希外连接大数据量等值外连接保留未匹配行放大中间结果;连接条件不完整造成多对多膨胀减少连接前数据与列宽;确认唯一性和完整连接键
HASH LEFT/RIGHT SEMI JOIN2哈希半连接;(ANTI) 表示反连接IN/EXISTS/NOT EXISTS子查询输入大、数据倾斜;NOT IN 的 NULL 语义易被误改先过滤子查询;收集连接列统计;改写前验证 NULL 语义
HASH LEFT SEMI MULTIPLE JOIN多列 IN/NOT IN 的哈希半连接多列存在性判断多列相关性缺失导致基数误判收集相关列统计;缩小子查询;不要随意拆分多列语义
HASH RIGHT SEMI JOIN32SOME/ANY/ALL 等子查询半连接any_optionsKEY_NULL_EQU复杂 NULL 与比较语义、估算偏差先验证业务语义,再做等价改写
MERGE INNER JOIN3归并内连接两侧输入按连接键有序无序输入需额外排序,可能落盘利用有序索引;连接前过滤;比较排序成本与哈希成本
MERGE SEMI JOIN3归并半连接;可带 (ANTI)有序的 EXISTS/NOT EXISTS两侧顺序建立成本检查是否已有顺序,避免不必要排序
MLO/MRO/MFOMERGE LEFT/RIGHT/FULL OUTER JOIN;本文新实例的动态视图已收录,当前官方附录尚未列出两侧有序的外连接归并前排序和未匹配行膨胀减少输入并利用顺序;以目标构建的实际计划为准

2.4 集合、子查询与临时结果
#

操作符/计划显示官方含义常见形态或关键字段性能关注点常见调优方向
UNION集合并并去重常被实现为 UNION ALL + DISTINCT去重需要内存或临时空间语义允许时使用 UNION ALL;各分支分别下推过滤
UNION ALL合并结果并保留重复各分支行数分支全扫的成本会叠加精简每个分支;避免重复访问同一大表
UNION ALL(MERGE)归并多个有序输入merge_typen_merge_keys顺序建立成本和多路归并压力利用已有顺序;减少分支输出
UNION FOR OR2将 OR 条件拆分后合并并按需去重各分支访问路径、去重键某一分支不可索引时仍可能全扫;重叠分支需去重同列等值 OR 可改为 IN;确保各分支都可有效访问
INTERSECT/INTERSECT ALL交集,ALL 保留重复两侧行数、重复语义比较和去重消耗内存或临时空间先缩小两侧;改写 EXISTS 时验证重复和 NULL 语义
EXCEPT/EXCEPT ALL差集,ALL 保留重复两侧行数、重复语义大结果比较与去重先过滤;改写 NOT EXISTS 时验证语义
NTTS2临时存放并向父节点传递数据is_atom、实际物化行数中间结果过大导致临时空间压力提前过滤和投影;检查是否能流式处理
SPL2按编号和 KEY 定位的临时数据集spool_numkey_numhas_varresult_cache物化结果大或外层变量取值多缩小物化结果;尝试去相关化为连接
HEAP TABLE/HEAP TABLE SCAN建立并扫描临时堆结果table_no、重复扫描次数大量落盘或反复扫描减少物化数据;检查重复引用和复杂视图
PIPE2处理左孩子时触发右孩子并匹配过滤左右实际行数、右侧进入次数相关子查询随左侧反复执行去相关化;索引关联列;用 ET 验证重复次数
CTE_SCN递归 WITH 扫描查询名、终止条件终止不严或每轮数据膨胀尽早终止与过滤;控制重复;索引递归关联列
CONST VALUE LIST构造常量行集row_numcol_num超大 IN 列表增加解析与匹配成本大批量值放临时表并按键连接
HIERARCHICAL QUERY/CNNTBCONNECT BY 层次查询父子键、连接条件、去重/防环层级深、分支宽、存在环或子节点无索引索引父子键;限定根和深度;使用正确防环语义

2.5 DML、控制、分区与分布式操作符
#

操作符/计划显示官方含义常见形态或关键字段性能关注点常见调优方向
INSERT/UPDATE/DELETE插入、更新、删除目标表、类型、下层定位路径目标行全扫、索引维护、触发器、锁等待和大事务为定位条件建索引;控制事务批量;精简无用索引;检查触发器与锁
MERGE INTO条件合并数据源目标连接与 DML 分支源重复导致目标多次匹配;连接和写入成本叠加保证源键唯一;索引匹配键;分别分析源读取和目标写入
UFLTUPDATE FROM 的目标 ROWID 检查/去重IS_TOP_1同一目标行被源连接匹配多次从数据模型和连接条件保证唯一,不用 FIRST/LAST 掩盖错误
LOCK TID/LTID对目标记录上锁等待时间、事务持续时间、影响行数热点更新、长事务和大范围 DML 锁竞争缩短事务;一致的更新顺序;精确命中行;分散热点
MVCC CHECK/MVCK2多版本可见性检查版本链、事务状态长事务和高更新表增加版本检查成本控制长事务;关注撤销与热点更新
ACTRL自适应计划控制主计划、备用计划及实际选择不同数据分布下可能选择不同路径,排查易只看到候选之一同时分析主备路径和实际执行;先修正统计信息
PARALLEL/PLL水平分区子表扫描与裁剪scan_typekey_num、分区范围未裁剪访问全部分区;数据倾斜让分区谓词可裁剪;匹配分区键类型;看最慢分区
GIDMDPC Granule Iterator;控制各工作线程的数据访问粒度和分区表裁剪policygi_unitscan_type粒度过大负载不均,过小调度开销高结合分区大小、线程数和倾斜调整
HPM水平分区结果归并排序order_keystop_flag分区多或倾斜时归并成为瓶颈保持分区内有序;下推 Top-N;治理倾斜
LOCAL BROADCAST/DISTRIBUTE本地并行线程间广播/重分发行数、列宽、分发键、线程数大结果复制、键倾斜、通信成本只广播小结果;分发前先过滤聚合;选择均匀键
LOCAL GATHER/COLLECT汇集本地并行线程结果,COLLECT 还负责同步for_sync、输出量主线程成为串行汇聚瓶颈在线程内先过滤和部分聚合;减少最终明细
LOCAL SCATTER/LSEND/LRECV本地线程消息散播或新 LPQ 发送/接收发送量、线程数并行任务过细,通信超过计算收益大任务再提高并行度;不要盲目设最大并行度
MPP BROADCAST/SCATTEREP 间广播或主站点向从站点发送数据量、站点数大表广播造成近似“数据量 × 站点数”的成本只广播小表或小结果;先过滤投影聚合
MPP DISTRIBUTE按键在 EP 间重分发分发键、站点行数网络量大、键倾斜、分布键不匹配让分布键匹配常用连接/分组键;监控各 EP 行数
MPP GATHER/COLLECT汇集数据到主 EP;COLLECT 增加同步输出量、for_synctop_flag主 EP 网络、CPU 或缓存成为瓶颈从 EP 完成局部聚合;减少集中明细
ESEND/ERECVDPC 子任务间发送和接收stask_notypesitesPV_FLAG网络交换、接收端倾斜、链路阻塞检查分发方式与键;只传必要列;利用分区智能连接

2.6 新版本扩展操作符
#

本文最新 2026-07-07 构建的 V$SQL_NODE_NAME 还返回下列节点。它们是“目标实例动态视图确认”的版本新增项;截至本文验证日,当前官方操作符附录尚未逐项给出这些节点的完整参数说明,因此这里只按实例返回的 DESC_CONTENT 标注,不把缩写继续扩写为未经证实的实现细节:

内部名DESC_CONTENT阅读建议
VSEKVECTOR INDEX SEEK实例确认的向量索引定位节点;具体算法、参数和适用范围以目标版本相关功能文档与完整计划为准
IESCN/IESCN2INDEX EXTENT SCAN / INDEX EXTENT SCAN2按实际计划参数解释,不凭缩写推断扫描范围或成本
IXSCNINDEX XDESC SCAN核对完整计划中的扫描方向、范围和输出行数
IRSCNINDEX RECORD SCAN结合父子节点判断数据来源及是否还需其他定位操作
IPIPEINDEX PIPE重点观察父子数据流、重复进入次数和下层访问量
ISENDINDEX SEND重点观察发送量、接收方及所处的并行或分布式上下文

3. EXPLAIN、AUTOTRACE 与 ET
#

ET 与 EXPLAIN、AUTOTRACE 同属计划分析工具:EXPLAIN 回答“预计怎样执行”,AUTOTRACE 回答“实际执行了什么”,ET 继续回答“时间和资源消耗集中在哪个节点”。

本章示例沿用第 4 章创建的 DMPLAN_ORDER 测试对象。若要同步执行,请先完成第 4 章;只阅读方法可直接继续。

3.1 工具用法对比
#

工具是否执行目标 SQL主要输出适合场景关键边界
EXPLAIN SQL文本计划、三元组、索引、扫描范围、谓词快速查看预计计划没有实际行数和运行资源
EXPLAIN [AS name] FOR SQL结构化计划、CPU/IO COST、条件、分区、建议保存和对比计划计划表位置与生命周期依构建而异
AUTOTRACE TRACE结果集、实际计划、预计→实际行数、读量和时间单条 SQL 实测会真正执行目标 SQL
AUTOTRACE TRACEONLY与 TRACE 类似,但不打印查询结果集内容大结果集测试DML 仍可能修改数据
ET(exec_id)SQL 已执行节点耗时、占比、N_ENTER、内存和磁盘定位实际热点需要监控、权限、有效执行号和完整取数

3.2 先确认版本和实例支持的节点
#

SELECT * FROM V$VERSION;

SELECT TYPE$, NAME, DESC_CONTENT, VERSION
FROM V$SQL_NODE_NAME
ORDER BY TYPE$;

本文实例返回 167 个节点。

SQL> SELECT * FROM V$VERSION;

行号     BANNER
---------- ---------------------------------
1          DM Database Server 64 V8
2          DB Version: 0x7000d
3          03134284604-20260707-335949-20228
4          Msg Version: 6
5          Gsu level(5) cnt: 102

已用时间: 1.967(毫秒). 执行号:4501.
SQL> SELECT TYPE$, NAME, DESC_CONTENT, VERSION
2   FROM V$SQL_NODE_NAME
3   ORDER BY TYPE$;

行号     TYPE$       NAME   DESC_CONTENT                                      VERSION
---------- ----------- ------ ------------------------------------------------- -----------
1          190         CTX    SQL CONTROL NODE                                  0
2          191         DLCK   DICTIONARY LOCK                                   0
3          192         GSEK   GEOMETRY SEEK                                     2
4          193         HFINS4 HFS TAB INSERT FOR MASTER HORIZON PARTITION TABLE 2
5          194         STRX   SET TRANSACTION                                   0
6          195         SORT3  SORT                                              3
7          196         EHFD   DPC HUGE TABLE DELETE                             1
8          197         NLI2   NEST LOOP INNER JOIN                              0
9          198         TOPN2  TOP N                                             2
10         199         HI3    HASH INNER JOIN                                   2
11         200         CSCN2  CLUSTER SCAN                                      5

行号     TYPE$       NAME    DESC_CONTENT          VERSION
---------- ----------- ------- --------------------- -----------
12         201         PRJT2   PROJECT OPERATION     0
13         202         HAGR2   HASH AGGREGATION      6
14         203         AAGR2   ALL AGGREGATION       6
15         204         NSET2   RESULT SET            1
16         205         SLCT2   SELECT                1
17         206         SAGR2   SORT AGGREGATION      5
18         207         CTE_SCN RECURSIVE WITH SCAN   0
19         208         EHFU    DPC HUGE TABLE UPDATE 3
20         209         NTTS2   TEMP TABLE SPOOL      0
21         210         DELETE2 DELETE                4
22         211         INSERT2 INSERT                3

行号     TYPE$       NAME      DESC_CONTENT                            VERSION
---------- ----------- --------- --------------------------------------- -----------
23         212         NAST2     ASSERT OPERATION                        0
24         213         MVCK2     MULTI VERSION CONCURRENCY CONTROL CHECK 1
25         214         IJI2      NEST LOOP INDEX JOIN                    0
26         215         UPDATE2   UPDATE                                  7
27         216         SLTIN2    SELECT INTO                             0
28         217         CSEK2     CLUSTER INDEX SEEK                      3
29         218         BLKUP2    BOOKMARK LOOKUP                         3
30         219         SSEK2     SECONDARY INDEX SEEK                    3
31         220         NLS2      NEST LOOP SEMI JOIN                     1
32         221         HLS2      HASH LEFT SEMI JOIN                     2
33         222         UNION_OR2 OR OPERATION DM                         0

行号     TYPE$       NAME       DESC_CONTENT                      VERSION
---------- ----------- ---------- --------------------------------- -----------
34         223         HRS2       HASH RIGHT SEMI JOIN              2
35         224         UNION_ALL2 UNION ALL OPERATION               0
36         225         UNION2     UNION OPERATION                   1
37         226         NLLO2      NEST LOOP LEFT OUTER JOIN         1
38         227         HLO2       HASH LEFT OUTER JOIN              2
39         228         HRO2       HASH RIGHT OUTER JOIN             2
40         229         HFO2       HASH FULL OUTER JOIN              2
41         230         HRS32      HASH RIGHT SEMI JOIN FOR SUBQUERY 1
42         231         IJS2       NEST LOOP INDEX SEMI JOIN         1
43         232         NLFO2      NEST LOOP FULL OUTER JOIN         0
44         233         PIPE2      PIPE                              0

行号     TYPE$       NAME   DESC_CONTENT                    VERSION
---------- ----------- ------ ------------------------------- -----------
45         234         SPL2   DATA SET SPOOLING               2
46         235         MS3    MERGE SEMI JOIN                 0
47         236         MI3    MERGE INNER JOIN                0
48         237         HLS12  HASH LEFT SEMI                  1
49         238         IJLO2  NEST LOOP INDEX LEFT OUTER JOIN 1
50         239         NCUR2  CURSOR OPERATION                0
51         240         CONSTV CONST VALUE LIST                1
52         241         HTAB   HEAP TABLE                      0
53         242         HSCN   HEAP TABLE SCAN                 0
54         243         LTID   LOCK TID                        0
55         244         FBTR   FAST BUILD BTREE                2

行号     TYPE$       NAME        DESC_CONTENT              VERSION
---------- ----------- ----------- ------------------------- -----------
56         245         EXCEPT2     EXCEPT                    0
57         246         EXCEPT_ALL2 EXCEPT ALL                0
58         248         INTER2      INTERSECT                 0
59         249         INTER_ALL2  INTERSECT ALL             0
60         250         UFLT        UPDATE FROM FILTER        0
61         251         ESEND       DPC SEND                  3
62         252         CNNTB       CONNECT BY OPERATOR       4
63         253         ERECV       DPC RECV                  2
64         254         FAGR2       FAST AGGREGATION          5
65         255         DSCN        DYNAMIC TABLE SCAN        1
66         256         VPI         VERTICAL PARTITION INSERT 0

行号     TYPE$       NAME       DESC_CONTENT                 VERSION
---------- ----------- ---------- ---------------------------- -----------
67         257         VPU        VERTICAL PARTITION UPDATE    0
68         258         VPD        VERTICAL PARTITION DELETE    0
69         259         PLL        HORIZON PARTITION PARALLIZE  2
70         260         MERGE INTO MERGE INTO                   2
71         261         SSCN       SECOND INDEX SCAN            4
72         262         HFD        HFS TAB DELETE WITH DELTA    2
73         263         HFD_EP     HFS TAB DELETE EP WITH DELTA 2
74         264         HFU        HFS TAB UPDATE WITH DELTA    5
75         265         HFU_EP     HFS TAB UPDATE EP WITH DELTA 4
76         266         EDIS       ECS DISTRIBUTE               0
77         267         EGAT       ECS GATHER                   0

行号     TYPE$       NAME    DESC_CONTENT                            VERSION
---------- ----------- ------- --------------------------------------- -----------
78         268         HLSM    HASH LEFT ANTI SEMI FOR MULTIPLE COLUMN 2
79         269         INSERT3 INSERT3                                 2
80         270         BRO     MPP BROADCAST                           0
81         271         DIS     MPP DISTRIBUTE                          1
82         272         GAT     MPP GATHER                              1
83         273         SCT     MPP SCATTER                             0
84         274         INSEP   INSERT EP                               0
85         275         EDEL    DPC DELETE                              4
86         276         DELEP   DEL EP                                  0
87         277         EUPD    DPC UPDATE                              6
88         278         UPDEP   UPDATE EP                               1

行号     TYPE$       NAME     DESC_CONTENT          VERSION
---------- ----------- -------- --------------------- -----------
89         279         EHFINS   DPC HUGE TABLE INSERT 0
90         280         SETEP    RESULT SET EP         0
91         281         EHFI     DPC HUGE TABLE INSERT 0
92         282         SLTINEP  SELECT INTO EP        0
93         283         RN       ROWNUM OPERATION      0
94         284         RNSK     ROWNUM STOP KEY       0
95         285         LINS     LIST TABLE INSERT     0
96         286         LUPD     LIST TABLE UPDATE     0
97         287         CONTAINS CONTEXT INDEX LEX     4
98         288         AFUN     ANALYTICAL FUNCTION   4
99         289         VWINS    VWINS                 0

行号     TYPE$       NAME  DESC_CONTENT                  VERSION
---------- ----------- ----- ----------------------------- -----------
100        290         PSCN  PARAMETER SCAN                1
101        291         ESCN  EXTERNAL SCAN                 0
102        292         VWUPD INSTEAD OF TRIGGER FOR UPDATE 0
103        293         VWDEL INSTEAD OF TRIGGER FOR DELETE 0
104        294         DIST  DISTINCT                      5
105        295         ASCN  RECORD ARRAY SCAN             3
106        296         RSCN  REMOTE SCAN                   0
107        297         LSET  LINK RESULT SET               0
108        298         LBRO  LOCAL BROADCAST               0
109        299         LDIS  LOCAL DISTRIBUTE              1
110        300         LGAT  LOCAL GATHER                  1

行号     TYPE$       NAME    DESC_CONTENT      VERSION
---------- ----------- ------- ----------------- -----------
111        301         LSCT    LOCAL SCATTER     0
112        302         EINS    DPC INSERT        3
113        303         HFSCN   HFS TAB SCAN      2
114        304         HFSEK   HFS TAB SEEK      2
115        305         HFLKUP  HFS TAB LOOKUP    2
116        306         HFDEL   HFS TAB DELETE    1
117        307         HFUPD   HFS TAB UPDATE    2
118        308         HFINS2  HFS TAB INSERT    1
119        309         HFINSEP HFS TAB INSERT EP 0
120        310         HFDELEP HFS TAB INSERT EP 0
121        311         HFUPDEP HFS TAB UPDATE EP 1

行号     TYPE$       NAME     DESC_CONTENT                      VERSION
---------- ----------- -------- --------------------------------- -----------
122        312         TERMINAL TERMINAL OPERATION                0
123        313         BMSEK    BITMAP INDEX SEEK                 0
124        314         BMCNT    BITMAP INDEX COUNT                0
125        315         BMAND    BITMAP INDEX BIT AND              0
126        316         BMOR     BITMAP INDEX BIT OR               0
127        317         BMCVT    BITMAP INDEX CONVERT BIT TO ROWID 0
128        318         VPIEP    VERTICAL PAR TAB INSERT           1
129        319         VPDEP    VERTICAL PAR TAB DELETE           0
130        320         VPUEP    VERTICAL PAR TAB UPDATE           1
131        321         BLKUPEP  BOOKMARK LOOKUP EP                1
132        322         GI       GRANULE ITERATOR                  0

行号     TYPE$       NAME            DESC_CONTENT                VERSION
---------- ----------- --------------- --------------------------- -----------
133        323         HFLKUPEP        HFS TAB LOOKUP EP           2
134        324         BMMG            BITMAP JOIN INDEX BIT MERGE 0
135        325         UNION_ALL_MERGE UNION ALL MERGE OPERATION   1
136        326         MCLCT           MPP COLLECT                 1
137        327         STAT            STATISTIC                   1
138        328         HFINS3          HFS TAB INSERT FOR MPP      2
139        329         RINS            REMOTE INSERT               0
140        330         RDEL            REMOTE DELETE               0
141        331         RUPD            REMOTE UPDATE               0
142        332         CONSTC          CONST COLUMN                1
143        333         FTTS            FILE TEMP TABLE SPOOL       0

行号     TYPE$       NAME      DESC_CONTENT                          VERSION
---------- ----------- --------- ------------------------------------- -----------
144        334         MSYNC     MPP SYNC                              0
145        335         LCLCT     LOCAL COLLECT                         1
146        336         ACTRL     ADAPTIVE CONTRL                       0
147        337         HPM       HORIZONTAL PARTITION TABLE MERGE SORT 0
148        338         HFI       HUGE TABLE INSERT                     2
149        339         HFI_EP    HUGE TABLE INSERT EP                  0
150        340         HFSCN2    HFS TAB SCAN                          2
151        341         HFSEK2    HFS TAB SEEK                          2
152        342         DSSEK     DISTINCT SKIP SEEK                    1
153        343         HFLKUP2   HFS TAB LOOKUP                        2
154        344         HFLKUPEP2 HFS TAB LOOKUP EP                     2

行号     TYPE$       NAME   DESC_CONTENT           VERSION
---------- ----------- ------ ---------------------- -----------
155        701         LSEND  NEW LPQ SEND           3
156        702         LRECV  NEW LPQ RECV           3
157        703         MODEL  SQL MODELING           0
158        706         VSEK   VECTOR INDEX SEEK      0
159        707         IESCN  INDEX EXTENT SCAN      2
160        708         MFO    MERGE FULL OUTER JOIN  0
161        709         MLO    MERGE LEFT OUTER JOIN  0
162        710         MRO    MERGE RIGHT OUTER JOIN 0
163        711         IXSCN  INDEX XDESC SCAN       0
164        712         IRSCN  INDEX RECORD SCAN      0
165        713         IESCN2 INDEX EXTENT SCAN2     0

行号     TYPE$       NAME  DESC_CONTENT VERSION
---------- ----------- ----- ------------ -----------
166        714         IPIPE INDEX PIPE   0
167        715         ISEND INDEX SEND   0

167 rows got

已用时间: 2.018(毫秒). 执行号:4502.
SQL>

3.3 用 EXPLAIN 看预计计划,用 EXPLAIN FOR 取结构化字段
#

EXPLAIN
SELECT CUSTOMER_ID, AMOUNT
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';

关键输出:

SQL> CONN DM_TEST
密码:

服务器[LOCALHOST:5236]:处于普通打开状态
登录使用时间 : 2.309(ms)
SQL> EXPLAIN
2   SELECT CUSTOMER_ID, AMOUNT
3   FROM DMPLAN_ORDER
4   WHERE STATUS = 'REFUND';

1   #NSET2: [1, 10000, 94]
2     #PRJT2: [1, 10000, 94]; exp_num(3), is_atom(FALSE); INFO_BITS(0); BATCH_EXP_NUM(0); spl_info(NULL)
3       #SSEK2: [1, 10000, 94]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), scan_range[('REFUND',min,min),('REFUND',max,max)), is_global(0)

已用时间: 1.977(毫秒). 执行号:0.
SQL>

需要字段化对比时:

EXPLAIN AS DMPLAN_DEMO FOR
SELECT CUSTOMER_ID, AMOUNT
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';

关键输出:

SQL> EXPLAIN AS DMPLAN_DEMO FOR
2   SELECT CUSTOMER_ID, AMOUNT
3   FROM DMPLAN_ORDER
4   WHERE STATUS = 'REFUND';

行号     PLAN_ID     PLAN_NAME   CREATE_TIME                LEVEL_ID    OPERATION TAB_NAME     IDX_NAME                                 SCAN_TYPE SCAN_RANGE                              ROW_NUMS             BYTES       COST
---------- ----------- ----------- -------------------------- ----------- --------- ------------ ---------------------------------------- --------- --------------------------------------- -------------------- ----------- --------------------
           CPU_COST             IO_COST              FILTER JOIN_COND ADVICE_INFO PSTART      PSTOP
           -------------------- -------------------- ------ --------- ----------- ----------- -----------
1          1           DMPLAN_DEMO 2026-08-18 21:55:02.000000 0           NSET2     NULL         NULL                                     NULL      NULL                                    10000                94          1
           1                    0                    NULL   NULL      NULL        0           0

2          1           DMPLAN_DEMO 2026-08-18 21:55:02.000000 1           PRJT2     NULL         NULL                                     NULL      NULL                                    10000                94          1
           1                    0                    NULL   NULL      NULL        0           0

3          1           DMPLAN_DEMO 2026-08-18 21:55:02.000000 2           SSEK2     DMPLAN_ORDER IDX_DPO_STATUS_CUST_AMOUNT[is_global(0)] ASC       [('REFUND',min,min),('REFUND',max,max)) 10000                94          1
           1                    0                    NULL   NULL      NULL        0           0


已用时间: 3.483(毫秒). 执行号:5301.
SQL>

结果集包含 PLAN_IDPLAN_NAMELEVEL_IDOPERATIONTAB_NAMEIDX_NAMESCAN_RANGEROW_NUMSBYTESCOSTCPU_COSTIO_COSTFILTERJOIN_CONDADVICE_INFOPSTARTPSTOP 等字段。

在本文实测构建中,计划写入 SYS."##PLAN_TABLE"

SELECT OWNER, TABLE_NAME, TEMPORARY, DURATION
FROM DBA_TABLES
WHERE TABLE_NAME = '##PLAN_TABLE';

-- 实测结果:OWNER=SYS,TEMPORARY=Y,DURATION=SYS$SESSION

SELECT PLAN_ID, PLAN_NAME, LEVEL_ID, OPERATION,
       TAB_NAME, IDX_NAME, SCAN_TYPE, SCAN_RANGE,
       ROW_NUMS, BYTES, COST, CPU_COST, IO_COST,
       FILTER, JOIN_COND, ADVICE_INFO, PSTART, PSTOP
FROM SYS."##PLAN_TABLE"
WHERE PLAN_NAME = 'DMPLAN_DEMO'
ORDER BY PLAN_ID, LEVEL_ID;

执行如下:

SQL> SELECT OWNER, TABLE_NAME, TEMPORARY, DURATION
2   FROM DBA_TABLES
3   WHERE TABLE_NAME = '##PLAN_TABLE';

行号     OWNER TABLE_NAME   TEMPORARY DURATION
---------- ----- ------------ --------- -----------
1          SYS   ##PLAN_TABLE Y         SYS$SESSION

已用时间: 14.598(毫秒). 执行号:5302.
SQL> SELECT PLAN_ID, PLAN_NAME, LEVEL_ID, OPERATION,
2          TAB_NAME, IDX_NAME, SCAN_TYPE, SCAN_RANGE,
3          ROW_NUMS, BYTES, COST, CPU_COST, IO_COST,
4          FILTER, JOIN_COND, ADVICE_INFO, PSTART, PSTOP
5   FROM SYS."##PLAN_TABLE"
6   WHERE PLAN_NAME = 'DMPLAN_DEMO'
7   ORDER BY PLAN_ID, LEVEL_ID;

行号     PLAN_ID     PLAN_NAME   LEVEL_ID    OPERATION TAB_NAME     IDX_NAME                                 SCAN_TYPE SCAN_RANGE                              ROW_NUMS             BYTES       COST                 CPU_COST             IO_COST
---------- ----------- ----------- ----------- --------- ------------ ---------------------------------------- --------- --------------------------------------- -------------------- ----------- -------------------- -------------------- --------------------
           FILTER JOIN_COND ADVICE_INFO PSTART      PSTOP
           ------ --------- ----------- ----------- -----------
1          1           DMPLAN_DEMO 0           NSET2     NULL         NULL                                     NULL      NULL                                    10000                94          1                    1                    0
           NULL   NULL      NULL        0           0

2          1           DMPLAN_DEMO 1           PRJT2     NULL         NULL                                     NULL      NULL                                    10000                94          1                    1                    0
           NULL   NULL      NULL        0           0

3          1           DMPLAN_DEMO 2           SSEK2     DMPLAN_ORDER IDX_DPO_STATUS_CUST_AMOUNT[is_global(0)] ASC       [('REFUND',min,min),('REFUND',max,max)) 10000                94          1                    1                    0
           NULL   NULL      NULL        0           0


已用时间: 2.191(毫秒). 执行号:5303.
SQL>

需要注意:

  • 同名 PLAN_NAME 再次执行会追加新的 PLAN_ID,不会覆盖旧记录;
  • 当前构建的计划表是会话级临时表,COMMITROLLBACK 不清空,但断开连接后数据消失;
  • 不同 DM8 构建可能显示不同属主,先查 DBA_TABLES,不要硬编码旧文章中的属主;
  • EXPLAIN 支持查看查询及多类 DML 的计划,但它不执行目标 DML。

3.4 AUTOTRACE 提供实际计划和语句级指标
#

先检查监控参数:

SELECT PARA_NAME, PARA_VALUE, SESS_VALUE, FILE_VALUE, PARA_TYPE
FROM V$DM_INI
WHERE PARA_NAME IN (
    'ENABLE_MONITOR',
    'MONITOR_SQL_EXEC',
    'ENABLE_MONITOR_DMSQL',
    'MONITOR_TIME'
);

当前官方 DIsql 文档要求 ENABLE_MONITORMONITOR_SQL_EXECENABLE_MONITOR_DMSQLTRACE/TRACEONLY 有效。ET 文档还涉及 MONITOR_TIME;本文构建未暴露该参数但仍取得了微秒级节点时间,因此应以目标版本的 V$DM_INI 和手册为准。

分析会话可设置:

SF_SET_SESSION_PARA_VALUE('MONITOR_SQL_EXEC', 1);

SET AUTOTRACE TRACEONLY;

SELECT CUSTOMER_ID, AMOUNT
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';

SET AUTOTRACE OFF;

TRACEONLY 只是抑制查询结果集内容,不会阻止 SQL 执行。对 DML 使用时仍可能修改数据。性能比较统一记录:

  • 预计→实际行数;
  • 逻辑读和物理读;
  • exec time(ms)
  • 节点 used timesN_ENTER
  • MEM_USEDDISK_USED
  • 返回行数和结果字节量。

同一 SQL 的多次运行可能因缓存、会话状态和测量波动产生不同数值。工具章节只说明取数方式,基准数据集中放在实战章节。

执行如下:

SQL> CONN SYSDBA
密码:

服务器[LOCALHOST:5236]:处于普通打开状态
登录使用时间 : 2.138(ms)
SQL> SELECT PARA_NAME, PARA_VALUE, SESS_VALUE, FILE_VALUE, PARA_TYPE
2   FROM V$DM_INI
3   WHERE PARA_NAME IN (
4       'ENABLE_MONITOR',
5       'MONITOR_SQL_EXEC',
6       'ENABLE_MONITOR_DMSQL',
7       'MONITOR_TIME'
8   );

行号     PARA_NAME            PARA_VALUE SESS_VALUE FILE_VALUE PARA_TYPE
---------- -------------------- ---------- ---------- ---------- ---------
1          ENABLE_MONITOR       1          1          1          SYS
2          MONITOR_SQL_EXEC     0          0          0          SESSION
3          ENABLE_MONITOR_DMSQL 1          1          1          SESSION

已用时间: 9.524(毫秒). 执行号:5501.
SQL> CONN DM_TEST
密码:

服务器[LOCALHOST:5236]:处于普通打开状态
登录使用时间 : 2.135(ms)
SQL> SF_SET_SESSION_PARA_VALUE('MONITOR_SQL_EXEC', 1);
DMSQL 过程已成功完成
已用时间: 1.559(毫秒). 执行号:5701.
SQL> SET AUTOTRACE TRACEONLY;
SQL> SELECT CUSTOMER_ID, AMOUNT
2   FROM DMPLAN_ORDER
3   WHERE STATUS = 'REFUND';

10000 rows got


1   #NSET2: [1, 10000->10000, 94] ; used times:1.617(ms); n_enter:43
2     #PRJT2: [1, 10000->10000, 94]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.016(ms); n_enter:82
3       #SSEK2: [1, 10000->10000, 94]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), is_global(0), scan_range[('REFUND',min,min),('REFUND',max,max)); used times:0.710(ms); n_enter:41

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        72                          logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        297250                      bytes sent to client
        198                         bytes received from client
        2                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        4.296                       exec time(ms)


已用时间: 2.099(毫秒). 执行号:5703.
SQL> SET AUTOTRACE OFF;
SQL>

3.5 用 ET 找到真实热点节点
#

执行目标 SQL 后,DIsql 页脚会显示“执行号”。把当前执行号代入 ET;本文原始实测的执行号为 247502

ET(<EXEC_ID>);

下面是对第 6.3 节哈希连接 SQL 再次执行后取得的 ET 摘要;它与实战表中的 AUTOTRACE 不是同一次执行,因此节点时间略有差异:

OPTIME(US)占比N_ENTERMEM_USED(KB)DISK_USED(KB)
HAGR223,73856.46%7851,6520
SSCN10,55025.09%78300
HI3(文本计划为 HASH2 INNER JOIN7,37717.55%1,60722,6560
CSCN23010.72%4100

占比分母包含未展示的轻量节点,不能只用表中四行的时间之和重新计算。也可以查询动态视图:

SELECT H.EXEC_ID,
       H.SEQ_NO,
       N.NAME AS OP,
       H.TIME_USED,
       H.N_ENTER,
       H.MEM_USED,
       H.DISK_USED
FROM V$SQL_NODE_HISTORY H
JOIN V$SQL_NODE_NAME N
  ON N.TYPE$ = H.TYPE$
WHERE H.EXEC_ID = <EXEC_ID>
ORDER BY H.TIME_USED DESC;

阅读 ET 时注意:

  • TIME_USED 单位为微秒;
  • N_ENTER 是操作符进入次数,不是输入或输出行数;
  • 节点耗时不能简单相加为语句总时间;
  • 查询结果应被客户端完整消费,否则统计可能不完整;
  • 历史视图容量有限,并发下记录可能被覆盖;
  • 非 DBA 用户可能需要动态视图查询权限和 SYS.ET 执行权限。

4. 构建测试环境
#

测试模型包含 1 万客户和 20 万订单。订单状态分布为 NEW 16 万行、PAID 3 万行、REFUND 1 万行。请在专用测试模式中执行,不要直接用于生产库。

4.1 建表并装载数据
#

-- 仅在专用测试模式执行;以下语句会删除同名测试表
DROP TABLE IF EXISTS DMPLAN_ORDER CASCADE;
DROP TABLE IF EXISTS DMPLAN_CUSTOMER CASCADE;

CREATE TABLE DMPLAN_CUSTOMER (
    CUSTOMER_ID   INT PRIMARY KEY,
    REGION_ID     INT NOT NULL,
    CUSTOMER_NAME VARCHAR(80),
    STATUS        CHAR(1),
    CREATED_AT    DATE
);

CREATE TABLE DMPLAN_ORDER (
    ORDER_ID    BIGINT PRIMARY KEY,
    CUSTOMER_ID INT NOT NULL,
    ORDER_DATE  DATE NOT NULL,
    STATUS      VARCHAR(12) NOT NULL,
    AMOUNT      DECIMAL(12, 2) NOT NULL,
    REMARK      VARCHAR(200)
);

INSERT INTO DMPLAN_CUSTOMER
SELECT LEVEL,
       MOD(LEVEL, 20) + 1,
       'CUSTOMER_' || TO_CHAR(LEVEL),
       CASE WHEN MOD(LEVEL, 10) = 0 THEN 'I' ELSE 'A' END,
       DATE '2024-01-01' + MOD(LEVEL, 365)
FROM DUAL
CONNECT BY LEVEL <= 10000;

INSERT INTO DMPLAN_ORDER
SELECT LEVEL,
       MOD(LEVEL, 10000) + 1,
       DATE '2025-01-01' + MOD(LEVEL, 365),
       CASE
           WHEN MOD(LEVEL, 20) = 0 THEN 'REFUND'
           WHEN MOD(LEVEL, 5) = 0 THEN 'PAID'
           ELSE 'NEW'
       END,
       MOD(LEVEL * 17, 100000) / 100.0,
       'ORDER_REMARK_' || TO_CHAR(LEVEL)
FROM DUAL
CONNECT BY LEVEL <= 200000;

COMMIT;

执行如下:

SQL> CONN DM_TEST
密码:

服务器[LOCALHOST:5236]:处于普通打开状态
登录使用时间 : 2.623(ms)
SQL> DROP TABLE IF EXISTS DMPLAN_ORDER CASCADE;
操作已执行
已用时间: 1.549(毫秒). 执行号:5001.
SQL> DROP TABLE IF EXISTS DMPLAN_CUSTOMER CASCADE;
操作已执行
已用时间: 1.528(毫秒). 执行号:5002.
SQL> CREATE TABLE DMPLAN_CUSTOMER (
2       CUSTOMER_ID   INT PRIMARY KEY,
3       REGION_ID     INT NOT NULL,
4       CUSTOMER_NAME VARCHAR(80),
5       STATUS        CHAR(1),
6       CREATED_AT    DATE
7   );
操作已执行
已用时间: 13.875(毫秒). 执行号:5003.
SQL> CREATE TABLE DMPLAN_ORDER (
2       ORDER_ID    BIGINT PRIMARY KEY,
3       CUSTOMER_ID INT NOT NULL,
4       ORDER_DATE  DATE NOT NULL,
5       STATUS      VARCHAR(12) NOT NULL,
6       AMOUNT      DECIMAL(12, 2) NOT NULL,
7       REMARK      VARCHAR(200)
8   );
操作已执行
已用时间: 9.037(毫秒). 执行号:5004.
SQL> INSERT INTO DMPLAN_CUSTOMER
2   SELECT LEVEL,
3          MOD(LEVEL, 20) + 1,
4          'CUSTOMER_' || TO_CHAR(LEVEL),
5          CASE WHEN MOD(LEVEL, 10) = 0 THEN 'I' ELSE 'A' END,
6          DATE '2024-01-01' + MOD(LEVEL, 365)
7   FROM DUAL
8   CONNECT BY LEVEL <= 10000;
影响行数 10000

已用时间: 31.191(毫秒). 执行号:5005.
SQL> INSERT INTO DMPLAN_ORDER
2   SELECT LEVEL,
3          MOD(LEVEL, 10000) + 1,
4          DATE '2025-01-01' + MOD(LEVEL, 365),
5          CASE
6              WHEN MOD(LEVEL, 20) = 0 THEN 'REFUND'
7              WHEN MOD(LEVEL, 5) = 0 THEN 'PAID'
8              ELSE 'NEW'
9          END,
10         MOD(LEVEL * 17, 100000) / 100.0,
11         'ORDER_REMARK_' || TO_CHAR(LEVEL)
12  FROM DUAL
13  CONNECT BY LEVEL <= 200000;
影响行数 200000

已用时间: 735.281(毫秒). 执行号:5006.
SQL> COMMIT;
操作已执行
已用时间: 2.392(毫秒). 执行号:5007.
SQL>

4.2 创建实验索引并收集统计信息
#

CREATE INDEX IDX_DPO_STATUS
    ON DMPLAN_ORDER(STATUS);

CREATE INDEX IDX_DPO_STATUS_CUST_AMOUNT
    ON DMPLAN_ORDER(STATUS, CUSTOMER_ID, AMOUNT);

CREATE INDEX IDX_DPO_CUSTOMER
    ON DMPLAN_ORDER(CUSTOMER_ID);

CREATE INDEX IDX_DPO_DATE
    ON DMPLAN_ORDER(ORDER_DATE);

CREATE INDEX IDX_DPC_REGION
    ON DMPLAN_CUSTOMER(REGION_ID);

统计信息是 CBO 估算选择率、基数和代价的基础。模式名应替换为实际对象属主:

-- DBMS_STATS 尚未创建时,由 DBA 执行一次
SP_CREATE_SYSTEM_PACKAGES(1, 'DBMS_STATS');

DBMS_STATS.GATHER_TABLE_STATS(
    'DM_TEST', 'DMPLAN_CUSTOMER', NULL,
    100, FALSE, 'FOR ALL COLUMNS SIZE AUTO',
    1, 'AUTO', TRUE
);

DBMS_STATS.GATHER_TABLE_STATS(
    'DM_TEST', 'DMPLAN_ORDER', NULL,
    100, FALSE, 'FOR ALL COLUMNS SIZE AUTO',
    1, 'AUTO', TRUE
);

GATHER_TABLE_STATS 会提交当前事务,不要把它插入未完成的业务事务。

执行如下:

SQL> CREATE INDEX IDX_DPO_STATUS
2       ON DMPLAN_ORDER(STATUS);
操作已执行
已用时间: 170.738(毫秒). 执行号:5008.
SQL> CREATE INDEX IDX_DPO_STATUS_CUST_AMOUNT
2       ON DMPLAN_ORDER(STATUS, CUSTOMER_ID, AMOUNT);
操作已执行
已用时间: 219.021(毫秒). 执行号:5009.
SQL> CREATE INDEX IDX_DPO_CUSTOMER
2       ON DMPLAN_ORDER(CUSTOMER_ID);
操作已执行
已用时间: 89.931(毫秒). 执行号:5010.
SQL> CREATE INDEX IDX_DPO_DATE
2       ON DMPLAN_ORDER(ORDER_DATE);
操作已执行
已用时间: 194.769(毫秒). 执行号:5011.
SQL> CREATE INDEX IDX_DPC_REGION
2       ON DMPLAN_CUSTOMER(REGION_ID);
操作已执行
已用时间: 14.189(毫秒). 执行号:5012.
SQL> CONN SYSDBA
密码:

服务器[LOCALHOST:5236]:处于普通打开状态
登录使用时间 : 2.136(ms)
SQL> SP_CREATE_SYSTEM_PACKAGES(1, 'DBMS_STATS');
DMSQL 过程已成功完成
已用时间: 233.661(毫秒). 执行号:5101.
SQL> DBMS_STATS.GATHER_TABLE_STATS(
2       'DM_TEST', 'DMPLAN_CUSTOMER', NULL,
3       100, FALSE, 'FOR ALL COLUMNS SIZE AUTO',
4       1, 'AUTO', TRUE
5   );
DMSQL 过程已成功完成
已用时间: 222.340(毫秒). 执行号:5102.
SQL> DBMS_STATS.GATHER_TABLE_STATS(
2       'DM_TEST', 'DMPLAN_ORDER', NULL,
3       100, FALSE, 'FOR ALL COLUMNS SIZE AUTO',
4       1, 'AUTO', TRUE
5   );
DMSQL 过程已成功完成
已用时间: 641.970(毫秒). 执行号:5103.
SQL>

4.3 用短查询验证数据,不保留冗长终端回显
#

SELECT COUNT(*) AS CUSTOMER_ROWS
FROM DMPLAN_CUSTOMER;

SELECT STATUS, COUNT(*) AS ORDER_ROWS
FROM DMPLAN_ORDER
GROUP BY STATUS
ORDER BY STATUS;

预期结果:

SQL> SELECT COUNT(*) AS CUSTOMER_ROWS
2   FROM DMPLAN_CUSTOMER;

行号     CUSTOMER_ROWS
---------- --------------------
1          10000

已用时间: 0.230(毫秒). 执行号:5738.
SQL>
SQL> SELECT STATUS, COUNT(*) AS ORDER_ROWS
2   FROM DMPLAN_ORDER
3   GROUP BY STATUS
4   ORDER BY STATUS;

行号     STATUS ORDER_ROWS
---------- ------ --------------------
1          NEW    160000
2          PAID   30000
3          REFUND 10000

已用时间: 11.792(毫秒). 执行号:5739.
SQL>

后续实验均先执行 SET AUTOTRACE TRACEONLY,完成后执行 SET AUTOTRACE OFF。为减少重复,以下 SQL 不再逐段展示这两行。

5. 实战:扫描、索引、回表与覆盖索引
#

扫描、索引定位、回表和覆盖共同回答一个问题:叶子访问路径如何影响总读量。下面先看高命中率,再看低命中率下的回表与覆盖。

5.1 命中 80% 数据时,聚集扫描优于强制索引回表
#

查询 NEW 订单,返回 20 万行中的 16 万行,并读取不在状态索引中的 REMARK

SELECT ORDER_ID, CUSTOMER_ID, AMOUNT, REMARK
FROM DMPLAN_ORDER
WHERE STATUS = 'NEW';

默认计划:

SQL> SF_SET_SESSION_PARA_VALUE('MONITOR_SQL_EXEC', 1);
DMSQL 过程已成功完成
已用时间: 0.261(毫秒). 执行号:5705.
SQL> SET AUTOTRACE TRACEONLY;
SQL> SELECT ORDER_ID, CUSTOMER_ID, AMOUNT, REMARK
2   FROM DMPLAN_ORDER
3   WHERE STATUS = 'NEW';

160000 rows got


1   #NSET2: [29, 160000->160000, 150] ; used times:37.510(ms); n_enter:222
2     #PRJT2: [29, 160000->160000, 150]; exp_num(5), is_atom(FALSE); INFO_BITS(0); used times:0.095(ms); n_enter:402
3       #SLCT2: [29, 160000->160000, 150]; DMPLAN_ORDER.STATUS = 'NEW', slct_pushdown(1); used times:0.050(ms); n_enter:402
4         #CSCN2: [29, 200000->200000, 150]; INDEX33555614(DMPLAN_ORDER); btr_scan(1); need_slct(1) prejudge_iescn(0); used times:30.576(ms); n_enter:201

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        1862                        logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        10296593                    bytes sent to client
        1429                        bytes received from client
        21                          roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        76.446                      exec time(ms)


已用时间: 2.327(毫秒). 执行号:5707.
SQL> 

用 Hint 做受控反例:

SELECT /*+ INDEX(O IDX_DPO_STATUS) */
       O.ORDER_ID, O.CUSTOMER_ID, O.AMOUNT, O.REMARK
FROM DMPLAN_ORDER O
WHERE O.STATUS = 'NEW';

强制索引后的计划:

SQL> SELECT /*+ INDEX(O IDX_DPO_STATUS) */
2          O.ORDER_ID, O.CUSTOMER_ID, O.AMOUNT, O.REMARK
3   FROM DMPLAN_ORDER O
4   WHERE O.STATUS = 'NEW';

160000 rows got


1   #NSET2: [186, 160000->160000, 150] ; used times:41.317(ms); n_enter:182
2     #PRJT2: [186, 160000->160000, 150]; exp_num(5), is_atom(FALSE); INFO_BITS(0); used times:0.111(ms); n_enter:322
3       #BLKUP2: [186, 160000->160000, 150]; IDX_DPO_STATUS(DMPLAN_ORDER); use_clu_addr(0); used times:216.982(ms); n_enter:322
4         #SSEK2: [186, 160000->160000, 150]; scan_type(ASC), IDX_DPO_STATUS(DMPLAN_ORDER), is_global(0), scan_range['NEW','NEW']; used times:4.295(ms); n_enter:161

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        480425                      logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        10296593                    bytes sent to client
        1479                        bytes received from client
        21                          roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        352.313                     exec time(ms)


已用时间: 3.542(毫秒). 执行号:5710.
SQL> 
方案主要节点实际行数逻辑读物理读执行时间
优化器默认CSCN2 → SLCT2160,0001,862076.446 ms
强制状态索引SSEK2 → BLKUP2160,000480,4250352.313 ms

两条 SQL 的语义、投影和返回行数相同,只改变访问路径。本次实测中,逻辑读约放大 258 倍。昂贵的不是 SSEK2 本身,而是 16 万候选行触发的大量回表。

CSCN2 的官方含义是聚集索引扫描。在普通行存表上,它常表现为整表式访问,但不是天然坏计划。计划里的 INDEX33555614 也可能是系统内部聚集索引名,不代表命中了业务二级索引。

5.2 低命中率仍要区分回表和覆盖
#

SSEK2 是否回表,要看有没有 BLKUP2,虽然REFUND 只有 1 万行。查询非覆盖列时:

SELECT ORDER_ID, CUSTOMER_ID, AMOUNT, REMARK
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';
SQL> SELECT ORDER_ID, CUSTOMER_ID, AMOUNT, REMARK
2   FROM DMPLAN_ORDER
3   WHERE STATUS = 'REFUND';

10000 rows got


1   #NSET2: [11, 10000->10000, 150] ; used times:3.694(ms); n_enter:44
2     #PRJT2: [11, 10000->10000, 150]; exp_num(5), is_atom(FALSE); INFO_BITS(0); used times:0.023(ms); n_enter:82
3       #BLKUP2: [11, 10000->10000, 150]; IDX_DPO_STATUS(DMPLAN_ORDER); use_clu_addr(0); used times:16.760(ms); n_enter:82
4         #SSEK2: [11, 10000->10000, 150]; scan_type(ASC), IDX_DPO_STATUS(DMPLAN_ORDER), is_global(0), scan_range['REFUND','REFUND']; used times:0.369(ms); n_enter:41

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        30061                       logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        641910                      bytes sent to client
        280                         bytes received from client
        3                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        28.467                      exec time(ms)


已用时间: 2.632(毫秒). 执行号:5713.
SQL>

实测为 30,061 次逻辑读、28.467 ms。节点耗时中,SSEK2 约 0.369 ms,BLKUP2 约 16.760 ms。

只返回组合索引覆盖的列时:

SELECT CUSTOMER_ID, AMOUNT
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';

组合索引 IDX_DPO_STATUS_CUST_AMOUNT(STATUS, CUSTOMER_ID, AMOUNT) 已包含过滤列和返回列:

SQL> SELECT CUSTOMER_ID, AMOUNT
2   FROM DMPLAN_ORDER
3   WHERE STATUS = 'REFUND';

10000 rows got


1   #NSET2: [1, 10000->10000, 94] ; used times:1.593(ms); n_enter:43
2     #PRJT2: [1, 10000->10000, 94]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.013(ms); n_enter:82
3       #SSEK2: [1, 10000->10000, 94]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), is_global(0), scan_range[('REFUND',min,min),('REFUND',max,max)); used times:0.705(ms); n_enter:41

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        44                          logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        297250                      bytes sent to client
        198                         bytes received from client
        2                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        2.489                       exec time(ms)


已用时间: 0.335(毫秒). 执行号:5715.
SQL>

这次没有 BLKUP2,实测为 44 次逻辑读、2.489 ms。

查询形态关键计划BLKUP2逻辑读执行时间
返回 REMARK 等非索引列SSEK2 → BLKUP230,06128.467 ms
只返回组合索引覆盖列SSEK2442.489 ms

这组实验说明如何从计划识别回表,以及覆盖查询如何减少整体读量。但两条 SQL 的投影列、结果字节数和所用索引不同,不能把全部时间差都归因于 BLKUP2

覆盖索引适合高频、高价值且返回列稳定的查询。宽索引会增加空间、缓存压力和 DML 维护成本;少量回表本身是正常现象。

6. 实战:聚合与连接算法
#

聚合和连接都不能只比较节点名称。判断的核心是输入是否有序、输入规模、连接语义、重复语义以及建立中间结构的成本。

6.1 输入顺序影响 SAGR2 与 HAGR2 的选择
#

基础 SQL:

SELECT STATUS, COUNT(*), SUM(AMOUNT)
FROM DMPLAN_ORDER
GROUP BY STATUS;

默认计划利用组合索引以 STATUS 为前导的顺序:

SQL> SELECT STATUS, COUNT(*), SUM(AMOUNT)
2   FROM DMPLAN_ORDER
3   GROUP BY STATUS;


1   #NSET2: [29, 3->3, 78] ; used times:0.197(ms); n_enter:5
2     #PRJT2: [29, 3->3, 78]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.007(ms); n_enter:8
3       #SAGR2: [29, 3->3, 78]; grp_num(1), sfun_num(2), opt_num(0), distinct_flag[0,0]; slave_empty(0) keys(DMPLAN_ORDER.STATUS); used times:10.814(ms); n_enter:205
4         #SSCN: [24, 200000->200000, 78]; IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER); btr_scan(1); is_global(0); used times:15.699(ms); n_enter:201

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        757                         logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        358                         bytes sent to client
        136                         bytes received from client
        1                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        28.601                      exec time(ms)


已用时间: 28.545(毫秒). 执行号:5717.
SQL> 

禁止该索引后,本次优化器选择聚集扫描和哈希聚合:

SELECT /*+ NO_INDEX(O IDX_DPO_STATUS_CUST_AMOUNT) */
       O.STATUS, COUNT(*), SUM(O.AMOUNT)
FROM DMPLAN_ORDER O
GROUP BY O.STATUS;
SQL> SELECT /*+ NO_INDEX(O IDX_DPO_STATUS_CUST_AMOUNT) */
2          O.STATUS, COUNT(*), SUM(O.AMOUNT)
3   FROM DMPLAN_ORDER O
4   GROUP BY O.STATUS;


1   #NSET2: [38, 3->3, 78] ; used times:0.204(ms); n_enter:3
2     #PRJT2: [38, 3->3, 78]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.004(ms); n_enter:4
3       #HAGR2: [38, 3->3, 78]; grp_num(1), sfun_num(2), MEM_USED(1661KB), DISK_USED(0KB), distinct_flag[0,0]; slave_empty(0) keys(O.STATUS); opt_info_bits(0); used times:17.402(ms); n_enter:203
4         #CSCN2: [24, 200000->200000, 78]; INDEX33555614(DMPLAN_ORDER); btr_scan(1); need_slct(0) prejudge_iescn(0); used times:20.471(ms); n_enter:201

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        1834                        logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        360                         bytes sent to client
        197                         bytes received from client
        1                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        38.248                      exec time(ms)


已用时间: 38.173(毫秒). 执行号:5720.
SQL>
方案输入路径聚合节点逻辑读内存/磁盘执行时间
默认有序二级索引扫描 SSCNSAGR2757未显示额外落盘28.601 ms
禁用组合索引聚集扫描 CSCN2HAGR21,8341,661 KB / 0 KB38.248 ms

这个实验比较的是“访问路径 + 聚合”的整体方案,不证明 SAGR2 天生比 HAGR2 快。SAGR2 需要有序输入;顺序可来自索引、SORT3、归并连接或其他保持顺序的节点。若建立顺序本身很贵,整体计划未必更优。

6.2 小驱动集和有效索引适合嵌套索引连接
#

SELECT /*+ USE_NL(C, O) */
       C.CUSTOMER_NAME, O.ORDER_ID, O.AMOUNT
FROM DMPLAN_CUSTOMER C
JOIN DMPLAN_ORDER O
  ON O.CUSTOMER_ID = C.CUSTOMER_ID
WHERE C.CUSTOMER_ID BETWEEN 1 AND 3;
SQL> SELECT /*+ USE_NL(C, O) */
2          C.CUSTOMER_NAME, O.ORDER_ID, O.AMOUNT
3   FROM DMPLAN_CUSTOMER C
4   JOIN DMPLAN_ORDER O
5     ON O.CUSTOMER_ID = C.CUSTOMER_ID
6   WHERE C.CUSTOMER_ID BETWEEN 1 AND 3;

60 rows got


1   #NSET2: [1, 59->60, 94] ; used times:0.208(ms); n_enter:5
2     #PRJT2: [1, 59->60, 94]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.004(ms); n_enter:8
3       #NEST LOOP INDEX JOIN2: [1, 59->60, 94] ; used times:0.006(ms); n_enter:12
4         #BLKUP2: [1, 2->3, 52]; INDEX33555613(DMPLAN_CUSTOMER); use_clu_addr(0); used times:0.018(ms); n_enter:4
5           #SSEK2: [1, 2->3, 52]; scan_type(ASC), INDEX33555613(DMPLAN_CUSTOMER), is_global(0), scan_range[1,3]; used times:0.027(ms); n_enter:2
6         #BLKUP2: [1, 20->60, 4]; IDX_DPO_CUSTOMER(DMPLAN_ORDER); use_clu_addr(0); used times:0.138(ms); n_enter:12
7           #SLCT2: [1, 20->60, 4]; (O.CUSTOMER_ID >= 1 AND O.CUSTOMER_ID <= 3), slct_pushdown(0); used times:0.022(ms); n_enter:12
8             #SSEK2: [1, 20->60, 4]; scan_type(ASC), IDX_DPO_CUSTOMER(DMPLAN_ORDER), is_global(0), scan_range[C.CUSTOMER_ID,C.CUSTOMER_ID]; used times:0.027(ms); n_enter:6

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        278                         logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        3130                        bytes sent to client
        251                         bytes received from client
        1                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        3.075                       exec time(ms)


已用时间: 3.001(毫秒). 执行号:5722.
SQL>

驱动侧实际只有 3 个客户,订单表的连接列上有索引。实测返回 60 行、278 次逻辑读、3.075 ms。这里嵌套索引连接很合适。

风险出现在驱动侧估算很小、实际却很大时:右孩子会被反复探测,索引定位和回表次数可能成倍增长。N_ENTER 可帮助确认反复进入,但它表示执行器调用/进入次数,不等于输出行数。

6.3 大结果集等值连接适合评估哈希连接
#

SELECT /*+ USE_HASH(C, O) */
       C.REGION_ID, COUNT(*), SUM(O.AMOUNT)
FROM DMPLAN_CUSTOMER C
JOIN DMPLAN_ORDER O
  ON O.CUSTOMER_ID = C.CUSTOMER_ID
GROUP BY C.REGION_ID;
SQL> SELECT /*+ USE_HASH(C, O) */
2          C.REGION_ID, COUNT(*), SUM(O.AMOUNT)
3   FROM DMPLAN_CUSTOMER C
4   JOIN DMPLAN_ORDER O
5     ON O.CUSTOMER_ID = C.CUSTOMER_ID
6   GROUP BY C.REGION_ID;

20 rows got


1   #NSET2: [53, 20->20, 42] ; used times:0.208(ms); n_enter:3
2     #PRJT2: [53, 20->20, 42]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.004(ms); n_enter:4
3       #HAGR2: [53, 20->20, 42]; grp_num(1), sfun_num(2), MEM_USED(1652KB), DISK_USED(0KB), distinct_flag[0,0]; slave_empty(0) keys(C.REGION_ID); opt_info_bits(0); used times:23.639(ms); n_enter:785
4         #HASH2 INNER JOIN: [39, 200000->200000, 42]; LKEY_UNIQUE KEY_NUM(1), MEM_USED(22656KB), DISK_USED(0KB) KEY(C.CUSTOMER_ID=O.CUSTOMER_ID) KEY_NULL_EQU(0); used times:7.311(ms); n_enter:1607
5           #CSCN2: [1, 10000->10000, 8]; INDEX33555612(DMPLAN_CUSTOMER); btr_scan(1); need_slct(0) prejudge_iescn(0); used times:0.276(ms); n_enter:41
6           #SSCN: [22, 200000->200000, 34]; IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER); btr_scan(1); is_global(0); used times:10.558(ms); n_enter:783

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        858                         logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        1080                        bytes sent to client
        237                         bytes received from client
        1                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        44.437                      exec time(ms)


已用时间: 44.344(毫秒). 执行号:5724.
SQL>

本次实测为 858 次逻辑读、44.437 ms。哈希连接节点使用约 22,656 KB 内存且没有落盘。

HASH JOIN 的核心是一侧构建哈希结构,另一侧按连接键探测。它不等于“没有索引时才用”,也不等于“先把小表去重”:

  • 即使存在索引,大结果集做大量回表可能仍不如顺序读取后哈希连接;
  • 两个孩子可以各自使用不同访问路径,包括索引访问;
  • 普通内连接必须保留重复匹配,不会为了建哈希表擅自改变重复语义;
  • 重点应看构建输入的实际行数与行宽、内存、磁盘使用和连接键倾斜。

6.4 归并连接要求输入有序;本例由索引提供顺序
#

SELECT C.CUSTOMER_ID
FROM DMPLAN_CUSTOMER C
WHERE EXISTS (
    SELECT 1
    FROM DMPLAN_ORDER O
    WHERE O.CUSTOMER_ID = C.CUSTOMER_ID
      AND O.STATUS = 'REFUND'
);
SQL> SELECT C.CUSTOMER_ID
2   FROM DMPLAN_CUSTOMER C
3   WHERE EXISTS (
4       SELECT 1
5       FROM DMPLAN_ORDER O
6       WHERE O.CUSTOMER_ID = C.CUSTOMER_ID
7         AND O.STATUS = 'REFUND'
8   );

500 rows got


1   #NSET2: [4, 10000->500, 16] ; used times:0.215(ms); n_enter:42
2     #PRJT2: [4, 10000->500, 16]; exp_num(2), is_atom(FALSE); INFO_BITS(0); used times:0.008(ms); n_enter:80
3       #MERGE SEMI JOIN3: [4, 10000->500, 16]; key_num(1)  KEY(C.CUSTOMER_ID=O.CUSTOMER_ID) KEY_NULL_EQU(0); used times:0.460(ms); n_enter:120
4         #SSCN: [1, 10000->9984, 16]; INDEX33555613(DMPLAN_CUSTOMER); btr_scan(1); is_global(0); used times:0.261(ms); n_enter:39
5         #SSEK2: [1, 10000->10000, 52]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), is_global(0), scan_range[('REFUND',min,min),('REFUND',max,max)); used times:0.270(ms); n_enter:41

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        143                         logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        11209                       bytes sent to client
        297                         bytes received from client
        2                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        3.968                       exec time(ms)


已用时间: 3.002(毫秒). 执行号:5726.
SQL>

实测输出 500 行、143 次逻辑读、3.968 ms。本例两侧分别由 SSCNSSEK2 提供顺序,因此它只证明当前计划利用了已有索引顺序。官方 SQL 调优文档说明,在 OPTIMIZER_MODE=1 并使用 ENHANCED_MERGE_JOIN 提示时,优化器会考虑插入 SORT3 实现归并;是否值得采用仍要比较排序与其他连接方案的整体成本。业务若要求最终结果有序,仍必须显式写 ORDER BY

EXISTS 改为 NOT EXISTS 时,DM 可能以带 (ANTI) 标记的反半连接实现。由于本文没有保留对应运行统计,这里只作为操作符阅读提示,不作为性能实测结论。

三类连接案例的 SQL 语义和数据规模不同,下表是证据索引,不是速度排名:

场景关键节点输出行数逻辑读执行时间主要观察点
3 行驱动集按索引查订单NEST LOOP INDEX JOIN2602783.075 ms右侧反复进入次数
20 万行等值连接后分组HASH2 INNER JOIN → HAGR22085844.437 ms22,656 KB 哈希内存,未落盘
存在性判断且两侧有序MERGE SEMI JOIN35001433.968 ms顺序来源和半连接语义

7. 实战:排序、窗口函数与集合运算
#

这组三个案例共同展示阻塞或结果集处理节点:即使最终结果很小,上游仍可能读取、排序或去重大量数据。

7.1 Top-N 不等于只读取 N 行
#

SELECT ORDER_ID, AMOUNT
FROM DMPLAN_ORDER
ORDER BY AMOUNT DESC
LIMIT 10;
SQL> SELECT ORDER_ID, AMOUNT
2   FROM DMPLAN_ORDER
3   ORDER BY AMOUNT DESC
4   LIMIT 10;

10 rows got


1   #NSET2: [37, 10->10, 50] ; used times:0.220(ms); n_enter:3
2     #PRJT2: [37, 10->10, 50]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.004(ms); n_enter:4
3       #SORT3: [37, 10->10, 50]; key_num(1), partition_key_num(0), is_distinct(FALSE), is_adaptive(0), MEM_USED(18432KB), DISK_USED(0KB); used times:6.354(ms); n_enter:203
4         #CSCN2: [23, 200000->200000, 50]; INDEX33555614(DMPLAN_ORDER); btr_scan(1); need_slct(0) prejudge_iescn(0); used times:15.547(ms); n_enter:201

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        1838                        logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        551                         bytes sent to client
        137                         bytes received from client
        1                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        23.987                      exec time(ms)


已用时间: 23.913(毫秒). 执行号:5728.
SQL>
输出行数上游读取行数逻辑读排序内存/磁盘执行时间
10200,0001,83818,432 KB / 0 KB23.987 ms

虽然最终只返回 10 行,但没有可利用的 AMOUNT 有序索引,仍需读取 20 万行。实测为 1,838 次逻辑读、18,432 KB 排序内存、23.987 ms,未落盘。

这类查询的优化重点是:尽早过滤、只投影必要列、建立与过滤条件和排序方向匹配的索引,以及把深分页改为基于上一页排序键的游标式分页。

7.2 窗口函数常见 SSEK2 → BLKUP2 → SORT3 → AFUN
#

SELECT ORDER_ID,
       CUSTOMER_ID,
       ROW_NUMBER() OVER (
           PARTITION BY CUSTOMER_ID
           ORDER BY ORDER_DATE DESC
       ) AS RNK
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';
SQL> SELECT ORDER_ID, AMOUNT
2   FROM DMPLAN_ORDER
3   ORDER BY AMOUNT DESC
4   LIMIT 10;

10 rows got


1   #NSET2: [37, 10->10, 50] ; used times:0.220(ms); n_enter:3
2     #PRJT2: [37, 10->10, 50]; exp_num(3), is_atom(FALSE); INFO_BITS(0); used times:0.004(ms); n_enter:4
3       #SORT3: [37, 10->10, 50]; key_num(1), partition_key_num(0), is_distinct(FALSE), is_adaptive(0), MEM_USED(18432KB), DISK_USED(0KB); used times:6.354(ms); n_enter:203
4         #CSCN2: [23, 200000->200000, 50]; INDEX33555614(DMPLAN_ORDER); btr_scan(1); need_slct(0) prejudge_iescn(0); used times:15.547(ms); n_enter:201

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        1838                        logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        551                         bytes sent to client
        137                         bytes received from client
        1                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        23.987                      exec time(ms)


已用时间: 23.913(毫秒). 执行号:5728.
SQL> SET AUTOTRACE TRACEONLY;
SQL> SELECT ORDER_ID,
2          CUSTOMER_ID,
3          ROW_NUMBER() OVER (
4              PARTITION BY CUSTOMER_ID
5              ORDER BY ORDER_DATE DESC
6          ) AS RNK
7   FROM DMPLAN_ORDER
8   WHERE STATUS = 'REFUND';

10000 rows got


1   #NSET2: [10, 10000->10000, 85] ; used times:5.026(ms); n_enter:43
2     #PRJT2: [10, 10000->10000, 85]; exp_num(4), is_atom(FALSE); INFO_BITS(0); used times:0.013(ms); n_enter:82
3       #AFUN: [10, 10000->10000, 85]; afun_num(1); used times:0.780(ms); n_enter:82
4         #SORT3: [10, 10000->10000, 85]; key_num(2), partition_key_num(0), is_distinct(FALSE), is_adaptive(0), MEM_USED(18432KB), DISK_USED(0KB); used times:3.061(ms); n_enter:82
5           #BLKUP2: [10, 10000->10000, 85]; IDX_DPO_STATUS(DMPLAN_ORDER); use_clu_addr(0); used times:18.409(ms); n_enter:82
6             #SSEK2: [10, 10000->10000, 85]; scan_type(ASC), IDX_DPO_STATUS(DMPLAN_ORDER), is_global(0), scan_range['REFUND','REFUND']; used times:0.514(ms); n_enter:41

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        30061                       logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        460324                      bytes sent to client
        323                         bytes received from client
        2                           roundtrips to/from client
        1                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        31.863                      exec time(ms)


已用时间: 25.916(毫秒). 执行号:5730.
SQL>
输出行数逻辑读内存排序次数排序内存/磁盘执行时间
10,00030,061118,432 KB / 0 KB31.863 ms

窗口结果由 AFUN 计算,但 PARTITION BY + ORDER BY 往往需要先建立顺序。多套不同窗口顺序可能触发多次排序,应先过滤、减少行宽并合并相同窗口定义。

7.3 UNIONUNION ALL 的差异是去重语义
#

SELECT CUSTOMER_ID
FROM DMPLAN_ORDER
WHERE STATUS = 'PAID'
UNION
SELECT CUSTOMER_ID
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';
SQL> SELECT CUSTOMER_ID FROM DMPLAN_ORDER WHERE STATUS = 'PAID'
2   UNION
3   SELECT CUSTOMER_ID FROM DMPLAN_ORDER WHERE STATUS = 'REFUND';

2000 rows got


1   #NSET2: [14, 10000->2000, 52] ; used times:0.611(ms); n_enter:161
2     #PRJT2: [14, 10000->2000, 52]; exp_num(1), is_atom(FALSE); INFO_BITS(0); used times:0.026(ms); n_enter:318
3       #DISTINCT: [14, 10000->2000, 52]; MEM_USED(0KB), DISK_USED(0KB); ; used times:0.886(ms); n_enter:319
4         #UNION ALL(MERGE): [11, 40000->40000, 52] merge_type(A); used times:1.515(ms); n_enter:320
5           #PRJT2: [4, 30000->30000, 52]; exp_num(1), is_atom(FALSE); INFO_BITS(0); used times:0.029(ms); n_enter:238
6             #SSEK2: [4, 30000->30000, 52]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), is_global(0), scan_range[('PAID',min,min),('PAID',max,max)); used times:0.860(ms); n_enter:119
7           #PRJT2: [1, 10000->10000, 52]; exp_num(1), is_atom(FALSE); INFO_BITS(0); used times:0.012(ms); n_enter:82
8             #SSEK2: [1, 10000->10000, 52]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), is_global(0), scan_range[('REFUND',min,min),('REFUND',max,max)); used times:0.391(ms); n_enter:41

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        203                         logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        44180                       bytes sent to client
        255                         bytes received from client
        2                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        6.935                       exec time(ms)


已用时间: 2.861(毫秒). 执行号:5732.
SQL>

改用 UNION ALL

SELECT CUSTOMER_ID
FROM DMPLAN_ORDER
WHERE STATUS = 'PAID'
UNION ALL
SELECT CUSTOMER_ID
FROM DMPLAN_ORDER
WHERE STATUS = 'REFUND';
SQL> SELECT CUSTOMER_ID FROM DMPLAN_ORDER WHERE STATUS = 'PAID'
2   UNION ALL
3   SELECT CUSTOMER_ID FROM DMPLAN_ORDER WHERE STATUS = 'REFUND';

40000 rows got


1   #NSET2: [11, 40000->40000, 52] ; used times:2.326(ms); n_enter:162
2     #PRJT2: [11, 40000->40000, 52]; exp_num(1), is_atom(FALSE); INFO_BITS(0); used times:0.022(ms); n_enter:318
3       #UNION ALL: [11, 40000->40000, 52]; used times:0.026(ms); n_enter:319
4         #PRJT2: [4, 30000->30000, 52]; exp_num(1), is_atom(FALSE); INFO_BITS(0); used times:0.029(ms); n_enter:238
5           #SSEK2: [4, 30000->30000, 52]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), is_global(0), scan_range[('PAID',min,min),('PAID',max,max)); used times:0.736(ms); n_enter:119
6         #PRJT2: [1, 10000->10000, 52]; exp_num(1), is_atom(FALSE); INFO_BITS(0); used times:0.004(ms); n_enter:82
7           #SSEK2: [1, 10000->10000, 52]; scan_type(ASC), IDX_DPO_STATUS_CUST_AMOUNT(DMPLAN_ORDER), is_global(0), scan_range[('REFUND',min,min),('REFUND',max,max)); used times:0.243(ms); n_enter:41

Statistics
-----------------------------------------------------------------
        0                           data pages changed
        0                           undo pages changed
        190                         logical reads
        0                           physical reads
        0                           direct physical reads
        0                           redo size
        880244                      bytes sent to client
        323                         bytes received from client
        3                           roundtrips to/from client
        0                           sorts (memory)
        0                           sorts (disk)
        0                           rows processed
        0.000                       io wait time(ms)
        5.789                       exec time(ms)


已用时间: 2.333(毫秒). 执行号:5733.
SQL>
写法关键节点输出行数逻辑读发送字节执行时间
UNIONUNION ALL(MERGE) → DISTINCT2,00020344,1806.935 ms
UNION ALLUNION ALL40,000190880,2445.789 ms

两条 SQL 的输出语义和行数不同,这不是同结果集的速度竞赛。只有业务确实要求去重时才使用 UNION;若两个分支投影后的结果集可证明不重叠且分支内部也无重复,或下游明确允许重复,UNION ALL 可以避免额外去重结构。

8. 总结:排查流程与常见误区
#

遇到慢 SQL 时,先按固定顺序检查,再对照后面的误区表。这样可以避免一看到全表扫描、回表或哈希连接就直接下结论。

8.1 慢 SQL 按这八步排查
#

  1. 先把现场记全。 保存实际 SQL 和绑定值,同时记录数据库构建号、表数据量、预期返回行数、并发量和变慢的时间。如果应用里慢、工具里快,就比较两边的绑定值、执行计划、事务状态、网络和取数方式。
  2. 确认统计信息是否可信。 看统计信息是不是最新的,关键列有没有统计,数据倾斜有没有被反映出来。统计信息不准,后面的行数估算和计划选择都可能跟着出错。
  3. 从计划最下面看怎么取数。 先确认读的是哪张表、哪个索引,是扫描还是范围定位,scan_range 有没有缩小范围。看到 SSEK2SSCN 时,再看上面有没有 BLKUP2,确认是否回表。
  4. 顺着计划向上看行数变化。 找出行数突然变多或大量减少的位置。连接后行数暴增,就检查连接条件和重复数据;读了很多行后才过滤,就检查条件为什么没有下推。
  5. 看连接方式是否适合当前数据量。 驱动集很小时看嵌套索引连接;大结果集的等值连接看哈希连接;两侧已有序时看归并连接。遇到半连接或反连接,还要确认 NULL 和重复值语义没有被改错。
  6. 找出最耗时、最占资源的节点。 重点看 SORT3HAGR2、HASH JOIN、DISTINCT、临时结果和数据重分发。结合实际行数、行宽、内存和落盘量判断它为什么慢。
  7. 用实际执行数据证明判断。 用 AUTOTRACE 看行数、读量和总耗时,用 ET 找最慢节点及其 N_ENTER、内存和磁盘用量。查询结果没有完整取完时,统计可能不完整。
  8. 一次只改一项再测试。 统计信息、索引、返回列、过滤条件、连接顺序和 Hint 要分开验证。每次修改后既要比较性能,也要确认结果与修改前一致。

每次测试记录一行,基准版本和调整后版本放在一起比较:

测试版本这次只改了什么关键计划实际处理/返回行数逻辑读/物理读总耗时最慢节点内存/落盘结果是否一致

8.2 常见误区与正确判断
#

误区正确判断
COST 就是执行毫秒数COST 是相对资源估值;实际时间看 AUTOTRACE 和 ET
第三个数字是节点总输出字节它是预计单行处理长度
把所有节点 COST 相加就是总成本父节点代价已经包含孩子代价
计划最深的节点只执行一次管道和嵌套循环会反复进入孩子节点;看 N_ENTER
见到 CSCN2 就建索引返回比例高时,聚集扫描可能比索引回表更好
INDEX335... 一定是业务索引它可能是系统内部聚集索引名
SSEK2 一定回表出现 BLKUP2 才明确表示需要定位基表取列
见到 BLKUP2 就建宽覆盖索引少量回表合理;覆盖索引会增加空间和写放大
HASH JOIN 只在没有索引时使用大结果集即使有索引,也可能更适合顺序读取后哈希连接
MERGE JOIN 必须有两个现成索引核心要求是两侧输入有序;本例由索引提供。在 OPTIMIZER_MODE=1 配合 ENHANCED_MERGE_JOIN 时可考虑补排序,仍须检查实际计划和总成本
HAGR/SAGR 只由有无索引决定关键是输入顺序及建立顺序的总成本
TRACEONLY 不执行 SQL它只是不打印查询结果集,目标 SQL 仍会执行
Hint 写上就一定生效Hint 可能被忽略;必须重新检查实际计划
一次耗时足以证明优化应区分冷/热缓存、并发、网络、取数和测量波动
统计信息更新后就清空全库计划缓存生产中会影响其他会话,应按对象和变更规范处理

最终判断应落在可复现证据上:同一语义、同一环境、一次只改一个变量,并同时比较实际行数、读量、时间与资源。操作符名称只告诉你数据库在做什么,不能单独证明它做得好或坏。

9. 参考资料
#

官方资料
#

  1. 达梦 DM8:查询优化:CBO、代价、访问路径、连接、统计信息和计划生成。
  2. 达梦 DM8:附录 4 执行计划操作符:操作符名称、参数和说明。
  3. 达梦 DM8:数据查询语句——EXPLAIN / EXPLAIN FOR:语法与结构化计划字段。
  4. 达梦 DM8:DIsql 环境变量设置:AUTOTRACE 模式、监控依赖和统计字段。
  5. 达梦 DM8:SQL 调优:扫描、回表、分组、连接、Hint 和计划历史。
  6. 达梦 DM8:DBMS_STATS 包:表、列和索引统计信息收集。
  7. 达梦 DM8:数据库优化 FAQ:ET、缓存计划和实际运行排错。