适用范围:达梦数据库 DM8。本文基于 DM8 单机版实测,核心分析方法同样适用于并行、MPP 和 DPC 环境;具体操作符、字段和参数以目标实例版本为准。
实测环境:
DM Database Server 64 V8,构建号03134284604-20260707-335949-20228,ARM64 Linux。实测日期:2026-08-18;结构修订日期:2026-08-19。测量口径:下文性能值来自同一测试环境中的单次实测;所列主要样本的物理读均为 0,因此不能外推为冷读性能。数据主要用于解释计划机制和相对差异,不代表其他硬件、数据分布或并发条件下的绝对性能。各实战对比表中的语句级执行时间统一采用 AUTOTRACE
Statistics的exec time(ms),不使用 DIsql 页脚的“已用时间”。
SQL 调优的可靠路径不是在计划里搜索“全表扫描”或“HASH JOIN”,然后机械地建索引、改 Hint。真正可靠的方法是:分析执行计划,先看数据库是怎么取数据的,再看预估行数和实际行数差多少,最后用逻辑读、执行时间、执行次数以及内存、磁盘使用情况来验证优化效果。
本文按“模型与方法 → 操作符 → 工具 → 环境 → 三组实战 → 排查总结”组织:
- 执行计划基础与阅读方法
- 常用操作符二维速查与性能判断
- EXPLAIN、AUTOTRACE 与 ET
- 构建测试环境
- 实战:扫描、索引、回表与覆盖索引
- 实战:聚合与连接算法
- 实战:排序、窗口函数与集合运算
- 总结:排查流程与常见误区
- 参考资料
1. 执行计划基础与阅读方法#
1.1 执行计划是优化器选择的执行方式#
同一条 SQL 往往存在多种访问路径、连接顺序和连接算法。DM 优化器根据对象统计信息、谓词选择率、索引、系统资源估值和 Hint 等因素,为候选方案估算代价并选择执行计划。
执行计划表达的是“数据库准备怎样取得结果”,不是 SQL 文本的逐句翻译。优化器可能:
- 把
EXISTS、IN转换为半连接,把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 access 和 filter 的区别#
执行计划后面经常还能看到谓词信息,例如:
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 读一棵计划,按这五步检查#
不要一看到全表扫描、回表或哈希连接就判断计划有问题。先从计划最下面的叶子节点开始,按数据向上流动的方向检查:
- 先看从哪里读数据。 确认访问的是哪张表、哪个索引,是扫描还是范围定位,
scan_range有没有真正缩小读取范围。 - 再看实际读了多少行。 比较每个节点的预计行数和实际行数,找到最早出现明显偏差的位置。
- 接着看行数在哪里变多、过滤在哪里发生。 如果下层读了很多行,到上层才过滤掉,说明过滤太晚;如果连接后行数突然变大,就检查连接条件和重复数据。
- 再找反复执行或占资源的节点。
N_ENTER很大,说明节点被多次进入;出现BLKUP2、SORT3、哈希或临时结果时,再看它们处理了多少行、用了多少内存、是否落盘。 - 最后看整条 SQL 是否真的更快。 比较逻辑读、物理读和执行时间,同时确认结果没有变化。COST 下降或某个节点变快,都不能单独证明优化成功。
先用这五步找到问题位置,再到下一章查对应操作符的含义、风险和调优方向。
2. 常用操作符二维速查与性能判断#
表里的操作符名称和基本含义,主要参考达梦官方文档和 V$SQL_NODE_NAME 为准。至于这个操作符有没有性能问题、应该怎么优化,不能只看名称,要结合实际处理行数、逻辑读、执行时间、内存和磁盘使用情况一起判断。
另外,操作符后面的 2、3 只是达梦内部不同版本或实现的标记,不代表数字越大性能就越好。
2.1 结果、访问与过滤#
| 操作符/计划显示 | 官方含义 | 常见形态或关键字段 | 性能关注点 | 常见调优方向 |
|---|---|---|---|---|
NSET2 | 收集结果集,通常位于根节点 | 预计→实际行数、行长 | 本身通常不是瓶颈;宽行会让整棵树搬运更多数据 | 向下寻找首个行数膨胀或耗时高的节点;避免无必要的 SELECT * |
PRJT2 | 投影与表达式计算 | exp_num、is_atom、输出行长 | 复杂函数、重复表达式、输出列过多增加 CPU 和数据搬运 | 只返回必要列;复用计算;必要时评估确定性函数索引 |
SLCT2 | 条件过滤 | 条件、slct_pushdown、过滤前后行数 | 过滤过晚;函数、隐式转换导致条件不能形成索引范围 | 改写为可索引表达式;统一数据类型;尽量下推高选择性条件 |
CSCN2 | 聚集索引扫描 | idxname(tabname)、btr_scan、need_slct | 大表只返回极少行时读取无关数据;但返回比例高时可能是最优 | 看表规模、返回比例和逻辑读;高选择性条件再考虑索引 |
CSEK2 | 聚集索引数据定位 | scan_type、scan_range | 范围过宽仍会读取大量记录 | 检查范围是否真正收窄;让索引列顺序匹配等值与范围条件 |
SSCN | 直接扫描整个二级索引 | 索引名、btr_scan、is_global | 不回表也可能扫完整个大索引 | 确认是否在服务覆盖查询、排序或分组;高选择性条件争取转为 SSEK2 |
SSEK2 | 二级索引按键值或范围定位 | scan_type、scan_range、is_global | 范围过宽,加回表后可能比顺序扫描更贵 | 检查组合索引前导列与范围;结合 BLKUP2 评估总成本 |
BLKUP2 | 根据二级索引记录定位基表记录 | 索引名、use_clu_addr、输入行数 | 大量离散回表造成随机读;嵌套循环中可能反复回表 | 减少返回列;提高过滤性;对高频查询评估覆盖索引;少量回表是正常的 |
DSSEK | DISTINCT 列上的索引跳跃扫描 | scan_type、scan_range | 索引顺序或数据分布不匹配时无法使用 | 让去重列与索引前导列匹配;与普通扫描加去重做实测比较 |
BMSEK/BMAND/BMOR/BMCVT/BMCNT/BMMG | 位图索引查找、与/或、ROWID 转换、计数和归并 | 位图范围、组合方式、转换后行数 | 高基数或频繁 DML 场景未必合适;候选行多仍会大量回表 | 用于低基数、读多写少场景;关注组合后选择率和回表量 |
DSCN | 动态视图扫描 | 动态视图名、过滤条件 | 监控视图数据量和实时计算开销 | 精确选择列与条件;避免高频无过滤轮询 |
ESCN | 外部表扫描 | 外部数据源、过滤条件 | 外部 I/O 和未下推过滤 | 尽量下推过滤与投影;减少外部数据读取 |
REMOTE SCAN/RSCN | DBLINK 远程表扫描 | table@dblink、远程 condition | 网络往返、未下推过滤、远端统计信息偏差 | 把过滤、投影、聚合推到远端;大中间集可先受控落地本地 |
HFSCN/HFSCN2 | HUGE 表逐行扫描 | 事务型/非事务型、表名 | 大范围读取带来大量 I/O | 利用分区与过滤;只选必要列 |
HFSEK/HFSEK2 | HUGE 表按 KEY 查找 | scan_type、scan_range | 范围过宽或 KEY 设计不匹配 | 检查 KEY 与访问模式、范围裁剪 |
HFLKUP/HFLKUP2 | HUGE 表按 ROWID 回查 | 输入行数 | 大量 lookup 放大随机访问 | 提高前置过滤性并减少回查列 |
2.2 聚合、排序、去重与分析#
| 操作符/计划显示 | 官方含义 | 常见形态或关键字段 | 性能关注点 | 常见调优方向 |
|---|---|---|---|---|
AAGR2 | 简单聚集;无分组时计算集函数 | sfun_num、distinct_flag | COUNT(DISTINCT ...) 仍可能消耗较大内存;下层可能扫描大量数据 | 先过滤;检查 MIN/MAX 或 DISTINCT 参数能否利用索引 |
FAGR2 | 快速聚集 | 无过滤 COUNT(*),或基于索引的 MIN/MAX | 通常风险较低;增加条件或复杂表达式后可能无法触发 | 保持语义正确,不要为了追求 FAGR 改变业务查询 |
HAGR2 | 哈希分组并计算集函数 | grp_num、keys、MEM_USED、DISK_USED | 高分组基数、倾斜或内存不足导致落盘 | 提前过滤和缩窄行;收集分组列统计信息;比较有序输入方案 |
SAGR2 | 对有序输入进行流式分组 | keys、下层有序来源 | 为获得顺序而额外排序可能抵消收益 | 利用以分组列为前导的索引;比较整个计划而非单一节点 |
SORT3 | 排序,也可能承担去重或 Top-N | key_num、is_distinct、top_flag、内存/磁盘 | 大行数、宽行、排序区不足导致临时 I/O | 提前过滤投影;利用有序索引;下推 Top-N |
DISTINCT/DIST | 删除重复行 | 输入行数、去重列、下层顺序 | 大结果去重消耗内存或落盘;可能掩盖错误的多对多连接 | 先检查连接条件;不要求去重就移除;评估 DSSEK |
TOPN2 | 取得前 N 行,支持偏移和百分比 | top_num、top_off、top_percent | 深分页仍读取或排序大量前置行;无顺序时结果不稳定 | 建立排序键索引;采用游标式分页;指定完整稳定排序键 |
RN | 生成或处理 ROWNUM | 与 TOPN2 的层级 | ROWNUM 与 ORDER BY 层级错误会改变语义 | 明确先排序还是先编号 |
RNSK | ROWNUM 停止条件处理 | rownum_exp | 未形成停止键时仍可能读取大量行 | 让上限条件尽早生效 |
AFUN | 分析/窗口函数计算 | partition_num、order_num | 大分区、宽行、多套窗口顺序带来排序与缓存压力 | 先过滤、减少列;合并相同窗口;评估匹配顺序的索引 |
2.3 连接与半连接#
| 操作符/计划显示 | 官方含义 | 适用形态 | 主要风险 | 调优方向 |
|---|---|---|---|---|
NEST LOOP INNER JOIN2 | 嵌套循环内连接 | 小驱动集、非等值连接或右侧可高效查找 | 驱动集大时右侧被反复扫描,成本近似乘法增长 | 让过滤后较小结果集驱动;校准基数;索引被驱动侧连接列 |
NEST LOOP INDEX JOIN2 | 利用索引的嵌套内连接 | 左侧每行驱动右侧 SSEK2/CSEK2 | 外侧实际行数被低估时产生海量索引探测和回表 | 控制驱动集;检查右侧 scan_range 和 N_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 JOIN32 | SOME/ANY/ALL 等子查询半连接 | any_options、KEY_NULL_EQU | 复杂 NULL 与比较语义、估算偏差 | 先验证业务语义,再做等价改写 |
MERGE INNER JOIN3 | 归并内连接 | 两侧输入按连接键有序 | 无序输入需额外排序,可能落盘 | 利用有序索引;连接前过滤;比较排序成本与哈希成本 |
MERGE SEMI JOIN3 | 归并半连接;可带 (ANTI) | 有序的 EXISTS/NOT EXISTS | 两侧顺序建立成本 | 检查是否已有顺序,避免不必要排序 |
MLO/MRO/MFO | MERGE LEFT/RIGHT/FULL OUTER JOIN;本文新实例的动态视图已收录,当前官方附录尚未列出 | 两侧有序的外连接 | 归并前排序和未匹配行膨胀 | 减少输入并利用顺序;以目标构建的实际计划为准 |
2.4 集合、子查询与临时结果#
| 操作符/计划显示 | 官方含义 | 常见形态或关键字段 | 性能关注点 | 常见调优方向 |
|---|---|---|---|---|
UNION | 集合并并去重 | 常被实现为 UNION ALL + DISTINCT | 去重需要内存或临时空间 | 语义允许时使用 UNION ALL;各分支分别下推过滤 |
UNION ALL | 合并结果并保留重复 | 各分支行数 | 分支全扫的成本会叠加 | 精简每个分支;避免重复访问同一大表 |
UNION ALL(MERGE) | 归并多个有序输入 | merge_type、n_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_num、key_num、has_var、result_cache | 物化结果大或外层变量取值多 | 缩小物化结果;尝试去相关化为连接 |
HEAP TABLE/HEAP TABLE SCAN | 建立并扫描临时堆结果 | table_no、重复扫描次数 | 大量落盘或反复扫描 | 减少物化数据;检查重复引用和复杂视图 |
PIPE2 | 处理左孩子时触发右孩子并匹配过滤 | 左右实际行数、右侧进入次数 | 相关子查询随左侧反复执行 | 去相关化;索引关联列;用 ET 验证重复次数 |
CTE_SCN | 递归 WITH 扫描 | 查询名、终止条件 | 终止不严或每轮数据膨胀 | 尽早终止与过滤;控制重复;索引递归关联列 |
CONST VALUE LIST | 构造常量行集 | row_num、col_num | 超大 IN 列表增加解析与匹配成本 | 大批量值放临时表并按键连接 |
HIERARCHICAL QUERY/CNNTB | CONNECT BY 层次查询 | 父子键、连接条件、去重/防环 | 层级深、分支宽、存在环或子节点无索引 | 索引父子键;限定根和深度;使用正确防环语义 |
2.5 DML、控制、分区与分布式操作符#
| 操作符/计划显示 | 官方含义 | 常见形态或关键字段 | 性能关注点 | 常见调优方向 |
|---|---|---|---|---|
INSERT/UPDATE/DELETE | 插入、更新、删除 | 目标表、类型、下层定位路径 | 目标行全扫、索引维护、触发器、锁等待和大事务 | 为定位条件建索引;控制事务批量;精简无用索引;检查触发器与锁 |
MERGE INTO | 条件合并数据 | 源目标连接与 DML 分支 | 源重复导致目标多次匹配;连接和写入成本叠加 | 保证源键唯一;索引匹配键;分别分析源读取和目标写入 |
UFLT | UPDATE FROM 的目标 ROWID 检查/去重 | IS_TOP_1 | 同一目标行被源连接匹配多次 | 从数据模型和连接条件保证唯一,不用 FIRST/LAST 掩盖错误 |
LOCK TID/LTID | 对目标记录上锁 | 等待时间、事务持续时间、影响行数 | 热点更新、长事务和大范围 DML 锁竞争 | 缩短事务;一致的更新顺序;精确命中行;分散热点 |
MVCC CHECK/MVCK2 | 多版本可见性检查 | 版本链、事务状态 | 长事务和高更新表增加版本检查成本 | 控制长事务;关注撤销与热点更新 |
ACTRL | 自适应计划控制 | 主计划、备用计划及实际选择 | 不同数据分布下可能选择不同路径,排查易只看到候选之一 | 同时分析主备路径和实际执行;先修正统计信息 |
PARALLEL/PLL | 水平分区子表扫描与裁剪 | scan_type、key_num、分区范围 | 未裁剪访问全部分区;数据倾斜 | 让分区谓词可裁剪;匹配分区键类型;看最慢分区 |
GI | DMDPC Granule Iterator;控制各工作线程的数据访问粒度和分区表裁剪 | policy、gi_unit、scan_type | 粒度过大负载不均,过小调度开销高 | 结合分区大小、线程数和倾斜调整 |
HPM | 水平分区结果归并排序 | order_keys、top_flag | 分区多或倾斜时归并成为瓶颈 | 保持分区内有序;下推 Top-N;治理倾斜 |
LOCAL BROADCAST/DISTRIBUTE | 本地并行线程间广播/重分发 | 行数、列宽、分发键、线程数 | 大结果复制、键倾斜、通信成本 | 只广播小结果;分发前先过滤聚合;选择均匀键 |
LOCAL GATHER/COLLECT | 汇集本地并行线程结果,COLLECT 还负责同步 | for_sync、输出量 | 主线程成为串行汇聚瓶颈 | 在线程内先过滤和部分聚合;减少最终明细 |
LOCAL SCATTER/LSEND/LRECV | 本地线程消息散播或新 LPQ 发送/接收 | 发送量、线程数 | 并行任务过细,通信超过计算收益 | 大任务再提高并行度;不要盲目设最大并行度 |
MPP BROADCAST/SCATTER | EP 间广播或主站点向从站点发送 | 数据量、站点数 | 大表广播造成近似“数据量 × 站点数”的成本 | 只广播小表或小结果;先过滤投影聚合 |
MPP DISTRIBUTE | 按键在 EP 间重分发 | 分发键、站点行数 | 网络量大、键倾斜、分布键不匹配 | 让分布键匹配常用连接/分组键;监控各 EP 行数 |
MPP GATHER/COLLECT | 汇集数据到主 EP;COLLECT 增加同步 | 输出量、for_sync、top_flag | 主 EP 网络、CPU 或缓存成为瓶颈 | 从 EP 完成局部聚合;减少集中明细 |
ESEND/ERECV | DPC 子任务间发送和接收 | stask_no、type、sites、PV_FLAG | 网络交换、接收端倾斜、链路阻塞 | 检查分发方式与键;只传必要列;利用分区智能连接 |
2.6 新版本扩展操作符#
本文最新 2026-07-07 构建的 V$SQL_NODE_NAME 还返回下列节点。它们是“目标实例动态视图确认”的版本新增项;截至本文验证日,当前官方操作符附录尚未逐项给出这些节点的完整参数说明,因此这里只按实例返回的 DESC_CONTENT 标注,不把缩写继续扩写为未经证实的实现细节:
| 内部名 | DESC_CONTENT | 阅读建议 |
|---|---|---|
VSEK | VECTOR INDEX SEEK | 实例确认的向量索引定位节点;具体算法、参数和适用范围以目标版本相关功能文档与完整计划为准 |
IESCN/IESCN2 | INDEX EXTENT SCAN / INDEX EXTENT SCAN2 | 按实际计划参数解释,不凭缩写推断扫描范围或成本 |
IXSCN | INDEX XDESC SCAN | 核对完整计划中的扫描方向、范围和输出行数 |
IRSCN | INDEX RECORD SCAN | 结合父子节点判断数据来源及是否还需其他定位操作 |
IPIPE | INDEX PIPE | 重点观察父子数据流、重复进入次数和下层访问量 |
ISEND | INDEX 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_ID、PLAN_NAME、LEVEL_ID、OPERATION、TAB_NAME、IDX_NAME、SCAN_RANGE、ROW_NUMS、BYTES、COST、CPU_COST、IO_COST、FILTER、JOIN_COND、ADVICE_INFO、PSTART 和 PSTOP 等字段。
在本文实测构建中,计划写入 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,不会覆盖旧记录; - 当前构建的计划表是会话级临时表,
COMMIT、ROLLBACK不清空,但断开连接后数据消失; - 不同 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_MONITOR、MONITOR_SQL_EXEC、ENABLE_MONITOR_DMSQL 对 TRACE/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 times与N_ENTER; MEM_USED与DISK_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 不是同一次执行,因此节点时间略有差异:
| OP | TIME(US) | 占比 | N_ENTER | MEM_USED(KB) | DISK_USED(KB) |
|---|---|---|---|---|---|
HAGR2 | 23,738 | 56.46% | 785 | 1,652 | 0 |
SSCN | 10,550 | 25.09% | 783 | 0 | 0 |
HI3(文本计划为 HASH2 INNER JOIN) | 7,377 | 17.55% | 1,607 | 22,656 | 0 |
CSCN2 | 301 | 0.72% | 41 | 0 | 0 |
占比分母包含未展示的轻量节点,不能只用表中四行的时间之和重新计算。也可以查询动态视图:
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 → SLCT2 | 160,000 | 1,862 | 0 | 76.446 ms |
| 强制状态索引 | SSEK2 → BLKUP2 | 160,000 | 480,425 | 0 | 352.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 → BLKUP2 | 是 | 30,061 | 28.467 ms |
| 只返回组合索引覆盖列 | SSEK2 | 否 | 44 | 2.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>| 方案 | 输入路径 | 聚合节点 | 逻辑读 | 内存/磁盘 | 执行时间 |
|---|---|---|---|---|---|
| 默认 | 有序二级索引扫描 SSCN | SAGR2 | 757 | 未显示额外落盘 | 28.601 ms |
| 禁用组合索引 | 聚集扫描 CSCN2 | HAGR2 | 1,834 | 1,661 KB / 0 KB | 38.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。本例两侧分别由 SSCN 和 SSEK2 提供顺序,因此它只证明当前计划利用了已有索引顺序。官方 SQL 调优文档说明,在 OPTIMIZER_MODE=1 并使用 ENHANCED_MERGE_JOIN 提示时,优化器会考虑插入 SORT3 实现归并;是否值得采用仍要比较排序与其他连接方案的整体成本。业务若要求最终结果有序,仍必须显式写 ORDER BY。
把 EXISTS 改为 NOT EXISTS 时,DM 可能以带 (ANTI) 标记的反半连接实现。由于本文没有保留对应运行统计,这里只作为操作符阅读提示,不作为性能实测结论。
三类连接案例的 SQL 语义和数据规模不同,下表是证据索引,不是速度排名:
| 场景 | 关键节点 | 输出行数 | 逻辑读 | 执行时间 | 主要观察点 |
|---|---|---|---|---|---|
| 3 行驱动集按索引查订单 | NEST LOOP INDEX JOIN2 | 60 | 278 | 3.075 ms | 右侧反复进入次数 |
| 20 万行等值连接后分组 | HASH2 INNER JOIN → HAGR2 | 20 | 858 | 44.437 ms | 22,656 KB 哈希内存,未落盘 |
| 存在性判断且两侧有序 | MERGE SEMI JOIN3 | 500 | 143 | 3.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>| 输出行数 | 上游读取行数 | 逻辑读 | 排序内存/磁盘 | 执行时间 |
|---|---|---|---|---|
| 10 | 200,000 | 1,838 | 18,432 KB / 0 KB | 23.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,000 | 30,061 | 1 | 18,432 KB / 0 KB | 31.863 ms |
窗口结果由 AFUN 计算,但 PARTITION BY + ORDER BY 往往需要先建立顺序。多套不同窗口顺序可能触发多次排序,应先过滤、减少行宽并合并相同窗口定义。
7.3 UNION 与 UNION 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>| 写法 | 关键节点 | 输出行数 | 逻辑读 | 发送字节 | 执行时间 |
|---|---|---|---|---|---|
UNION | UNION ALL(MERGE) → DISTINCT | 2,000 | 203 | 44,180 | 6.935 ms |
UNION ALL | UNION ALL | 40,000 | 190 | 880,244 | 5.789 ms |
两条 SQL 的输出语义和行数不同,这不是同结果集的速度竞赛。只有业务确实要求去重时才使用 UNION;若两个分支投影后的结果集可证明不重叠且分支内部也无重复,或下游明确允许重复,UNION ALL 可以避免额外去重结构。
8. 总结:排查流程与常见误区#
遇到慢 SQL 时,先按固定顺序检查,再对照后面的误区表。这样可以避免一看到全表扫描、回表或哈希连接就直接下结论。
8.1 慢 SQL 按这八步排查#
- 先把现场记全。 保存实际 SQL 和绑定值,同时记录数据库构建号、表数据量、预期返回行数、并发量和变慢的时间。如果应用里慢、工具里快,就比较两边的绑定值、执行计划、事务状态、网络和取数方式。
- 确认统计信息是否可信。 看统计信息是不是最新的,关键列有没有统计,数据倾斜有没有被反映出来。统计信息不准,后面的行数估算和计划选择都可能跟着出错。
- 从计划最下面看怎么取数。 先确认读的是哪张表、哪个索引,是扫描还是范围定位,
scan_range有没有缩小范围。看到SSEK2或SSCN时,再看上面有没有BLKUP2,确认是否回表。 - 顺着计划向上看行数变化。 找出行数突然变多或大量减少的位置。连接后行数暴增,就检查连接条件和重复数据;读了很多行后才过滤,就检查条件为什么没有下推。
- 看连接方式是否适合当前数据量。 驱动集很小时看嵌套索引连接;大结果集的等值连接看哈希连接;两侧已有序时看归并连接。遇到半连接或反连接,还要确认 NULL 和重复值语义没有被改错。
- 找出最耗时、最占资源的节点。 重点看
SORT3、HAGR2、HASH JOIN、DISTINCT、临时结果和数据重分发。结合实际行数、行宽、内存和落盘量判断它为什么慢。 - 用实际执行数据证明判断。 用 AUTOTRACE 看行数、读量和总耗时,用 ET 找最慢节点及其
N_ENTER、内存和磁盘用量。查询结果没有完整取完时,统计可能不完整。 - 一次只改一项再测试。 统计信息、索引、返回列、过滤条件、连接顺序和 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. 参考资料#
官方资料#
- 达梦 DM8:查询优化:CBO、代价、访问路径、连接、统计信息和计划生成。
- 达梦 DM8:附录 4 执行计划操作符:操作符名称、参数和说明。
- 达梦 DM8:数据查询语句——EXPLAIN / EXPLAIN FOR:语法与结构化计划字段。
- 达梦 DM8:DIsql 环境变量设置:AUTOTRACE 模式、监控依赖和统计字段。
- 达梦 DM8:SQL 调优:扫描、回表、分组、连接、Hint 和计划历史。
- 达梦 DM8:DBMS_STATS 包:表、列和索引统计信息收集。
- 达梦 DM8:数据库优化 FAQ:ET、缓存计划和实际运行排错。
