本文通过一组可复现的执行计划实验,介绍达梦数据库 Hint 的基本用法,并重点验证 9 个优化参数在不同取值下对执行计划的影响。最后再演示如何通过 Hint 注入,在不修改应用 SQL 的情况下改变执行计划。
原始 SQL
↓
优化器分析候选访问路径和改写方式
↓
估算每种方案的行数与代价
↓
选择执行计划
加入 Hint 后:
↓
限制、引导或调整候选方案
↓
重新生成执行计划
先给出本文的实验结论:
| 实验项 | 主要变化 |
|---|---|
INDEX / NO_INDEX |
索引等值扫描与全表扫描之间切换 |
USE_HASH / USE_NL |
HASH JOIN 与嵌套循环索引连接之间切换 |
ENABLE_IN_VALUE_LIST_OPT |
扫描后过滤变为常量值列表驱动的索引访问 |
OPTIMIZER_OR_NBEXP |
两个重叠 OR 范围合并为一个连续索引范围 |
TOP_ORDER_OPT_FLAG |
影响 TOP + ORDER BY 场景的索引选择倾向与估算行数 |
ENABLE_INDEX_FILTER |
过滤条件从回表后计算移动到索引访问阶段计算 |
TOP_DIS_HASH_FLAG |
TOP 下方从直接 HASH JOIN 变为带 NL 候选的自适应计划 |
ENABLE_RQ_TO_NONREF_SPL |
全量连接分组改写与逐行相关 SPL 执行之间切换 |
VIEW_PULLUP_FLAG |
视图上拉后进一步消除中间投影层 |
SUBQ_CVT_SPL_FLAG |
相关 SPL 转换为临时方法实现 |
BEXP_CALC_ST_FLAG |
本次常量表达式实验未观察到差异,说明触发场景具有条件限制 |
| Hint 注入 | 原 SQL 不写 Hint,计划仍从 HASH JOIN 变为 NEST LOOP INDEX JOIN |
说明:截图中的“已用时间”是
EXPLAIN生成计划所消耗的时间,不是 SQL 实际执行耗时。本文主要对比操作符、估算行数、代价和访问路径,不使用该时间直接判断 SQL 性能。
Hint 是写在 SQL 中的优化器提示,用来影响优化器的计划生成过程。常见写法如下:
SELECT /*+ HINT_NAME(arguments) */
column_list
FROM table_name
WHERE condition;
一条 SQL 可以同时包含多个 Hint:
SELECT /*+ INDEX(A, IDX_ORDER_SCORE)
OPTIMIZER_OR_NBEXP(8) */
A.ORDER_ID,
A.SCORE
FROM T_ORDER A
WHERE A.SCORE BETWEEN 100 AND 300
OR A.SCORE BETWEEN 200 AND 500;
达梦支持的 Hint 不只有 INDEX、USE_HASH 等固定提示,也允许将部分 INI 参数临时写入 SQL。可以通过以下视图确认参数是否支持 Hint:
SELECT PARA_NAME,
HINT_TYPE
FROM V$HINT_INI_INFO
WHERE PARA_NAME = 'ENABLE_IN_VALUE_LIST_OPT';
HINT_TYPE 常见值包括:
OPT:在优化分析阶段生效,主要影响执行计划生成;EXEC:在运行阶段生效,通常要求写在最外层 SQL 中。还可以查看当前参数值:
SELECT PARA_NAME,
PARA_VALUE
FROM V$DM_INI
WHERE PARA_NAME = 'ENABLE_IN_VALUE_LIST_OPT';
使用 Hint 时要注意三点:
EXPLAIN 检查 Hint 是否真正生效,并在真实数据量和并发条件下验证性能。本次实验环境:
数据库:DM Database Server x64 V8
版本标识:--03134284488-20260212-314192-20200
测试用户:SYSDBA
测试工具:disql
实验使用三张表:
T_CUSTOMER 1000 行
T_ORDER 10000 行
T_ORDER_DETAIL 30000 行
主要数据分布:
T_ORDER_DETAIL:每个订单对应 3 条明细
T_ORDER.SCORE:0~999,每个值约 10 行
T_ORDER.TEST_FLAG:值 1 约 9000 行,值 9 约 1000 行
T_ORDER.STATUS:存在明显分布差异,便于构造索引和连接实验
实验中使用的主要索引:
CREATE INDEX IDX_ORDER_STATUS
ON T_ORDER(STATUS);
CREATE INDEX IDX_ORDER_SCORE
ON T_ORDER(SCORE);
CREATE INDEX IDX_ORDER_CUSTOMER
ON T_ORDER(CUSTOMER_ID);
CREATE INDEX IDX_DETAIL_ORDER
ON T_ORDER_DETAIL(ORDER_ID);
CREATE INDEX IDX_CUSTOMER_LEVEL
ON T_CUSTOMER(CUSTOMER_LEVEL);
CREATE INDEX IDX_ORDER_STATUS_AMOUNT
ON T_ORDER(STATUS, ORDER_AMOUNT);
CREATE INDEX IDX_ORDER_TEST_FLAG
ON T_ORDER(TEST_FLAG);
| 操作符 | 本文中的简化理解 |
|---|---|
CSCN2 |
基表或聚集索引全扫描 |
SSCN |
二级索引全扫描 |
SSEK2 |
索引等值或范围扫描 |
BLKUP2 |
根据索引结果回表取其他列 |
SLCT2 |
执行过滤条件 |
HASH2 INNER JOIN |
哈希内连接 |
NEST LOOP INDEX JOIN2 |
嵌套循环索引连接 |
CONST VALUE LIST |
将 IN 列表转换成内部常量值集合 |
TOPN2 |
处理 TOP N |
SPL2 |
子查询或相关表达式的 SPL 执行节点 |
AAGR2 |
无分组项的聚合 |
HAGR2 |
哈希分组聚合 |
ACTRL |
自适应计划控制节点 |
PRJT2 |
查询项投影处理 |
在进入 9 个参数前,先用最直观的索引和连接 Hint 观察优化器计划如何变化。
INDEX 与 NO_INDEX:控制索引访问INDEX 用于提示优化器使用指定索引,NO_INDEX 用于禁止指定索引。
实验 SQL:
-- 默认计划
EXPLAIN
SELECT *
FROM T_ORDER A
WHERE A.STATUS = 1;
-- 指定状态索引
EXPLAIN
SELECT /*+ INDEX(A, IDX_ORDER_STATUS) */
*
FROM T_ORDER A
WHERE A.STATUS = 1;
-- 禁止状态索引
EXPLAIN
SELECT /*+ NO_INDEX(A, IDX_ORDER_STATUS) */
*
FROM T_ORDER A
WHERE A.STATUS = 1;
默认计划和 INDEX 计划都使用:
SSEK2 IDX_ORDER_STATUS
↓
BLKUP2 回表
使用 NO_INDEX 后变为:
CSCN2 全扫描
↓
SLCT2 过滤 STATUS=1
这说明 Hint 可以直接干预表的访问路径。不过默认计划本身已经选择了正确索引,因此显式 INDEX 并没有进一步改变结构;真正明显的变化来自 NO_INDEX。
USE_HASH 与 USE_NL:控制连接方式测试 SQL 连接订单表和订单明细表:
-- 默认计划
EXPLAIN
SELECT A.ORDER_ID,
B.AMOUNT
FROM T_ORDER A
JOIN T_ORDER_DETAIL B
ON B.ORDER_ID = A.ORDER_ID
WHERE A.STATUS = 1;
-- 强制 HASH JOIN
EXPLAIN
SELECT /*+ USE_HASH(A, B) */
A.ORDER_ID,
B.AMOUNT
FROM T_ORDER A
JOIN T_ORDER_DETAIL B
ON B.ORDER_ID = A.ORDER_ID
WHERE A.STATUS = 1;
-- 强制嵌套循环
EXPLAIN
SELECT /*+ USE_NL(A, B) */
A.ORDER_ID,
B.AMOUNT
FROM T_ORDER A
JOIN T_ORDER_DETAIL B
ON B.ORDER_ID = A.ORDER_ID
WHERE A.STATUS = 1;
默认计划中出现 ACTRL,同时保留 HASH 和 NL 两类候选路径;使用 USE_HASH 后,计划固定为:
HASH2 INNER JOIN
A:IDX_ORDER_STATUS
B:CSCN2 全扫描明细表
使用 USE_NL 后固定为:
NEST LOOP INDEX JOIN2
A:IDX_ORDER_STATUS
B:IDX_DETAIL_ORDER
对于过滤后外表行数较少、内表连接列有索引的场景,嵌套循环可以按外层订单号逐次探测明细索引;HASH JOIN 则更偏向先扫描并构建哈希结构。
先比较两种等价写法:
EXPLAIN
SELECT *
FROM T_ORDER A
WHERE A.STATUS = 1
OR A.STATUS = 2
OR A.STATUS = 3;
EXPLAIN
SELECT *
FROM T_ORDER A
WHERE A.STATUS IN (1, 2, 3);
在当前默认参数下,两条 SQL 最终都生成:
NEST LOOP INDEX JOIN2
├── CONST VALUE LIST:1、2、3
└── IDX_ORDER_STATUS 索引探测
说明优化器已经把多个同列等值 OR 统一处理成常量值列表,再对索引逐值探测。因此,SQL 写成 OR 或 IN 并不一定产生不同计划,最终还要看优化器参数和改写规则。
下面每个实验都先介绍参数含义和取值,再给出实验 SQL、计划变化和结论。位标志型参数允许将多个值相加组合使用。
ENABLE_IN_VALUE_LIST_OPT该参数控制 IN (...) 值列表的优化方式。本文环境默认值为 518,属于多个控制位的组合值。
| 值 | 含义 |
|---|---|
0 |
不优化 IN LIST |
1 |
语义分析阶段转换为 CONST VALUE LIST |
2 |
代价优化阶段转换为 CONST VALUE LIST |
4 |
生成传递闭包优化 |
16 |
OR 转 LIST IN LIST 时,允许列表列来自不同表 |
32 |
IN LIST 中包含分区列时允许转换为 SEMI JOIN |
64 |
多列 IN 左侧含非列表达式时,允许转换为常量值列表 |
128 |
分析阶段将 IN VALUE LIST 转换成 OR 表达式 |
256 |
多个 IN 作为索引连接条件时,先对 IN 表达式做连接,并限制分析阶段结果规模 |
512 |
多列常量 NOT IN 转换为 NULL_EQU,便于批量计算 |
1024 |
在值 1 关闭时,仍允许 JOIN 条件中的 IN LIST 在语义阶段转换为常量值列表 |
2048 |
调整 IN LIST 作为 JOIN CONDITION 与转换为常量值列表的代价倾向 |
4096 |
增强 OR 转 IN、AND 转 NOT IN 时按数据类型拆分的处理 |
8192 |
禁止在 PHA、PHB 阶段将 OR 转换为 IN LIST |
验证 STATUS IN (1,2,3) 在关闭和开启语义阶段常量列表转换时,执行计划如何变化。
EXPLAIN
SELECT /*+ ENABLE_IN_VALUE_LIST_OPT(0) */
*
FROM T_ORDER A
WHERE A.STATUS IN (1, 2, 3);
EXPLAIN
SELECT /*+ ENABLE_IN_VALUE_LIST_OPT(1) */
*
FROM T_ORDER A
WHERE A.STATUS IN (1, 2, 3);
值 0:
CSCN2 扫描 10000 行
↓
SLCT2 判断 STATUS IN LIST
↓
估算返回 2500 行
值 1:
CONST VALUE LIST:1、2、3
↓
NEST LOOP INDEX JOIN2
↓
IDX_ORDER_STATUS 逐值查找
ENABLE_IN_VALUE_LIST_OPT(1) 将 IN 列表转换为内部常量值集合,并以列表中的值驱动状态索引访问;值 0 则保留普通过滤结构,先扫描再判断 IN 条件。
SUBQ_CVT_SPL_FLAG该参数控制相关子查询的多种实现和改写方式。
| 值 | 含义 |
|---|---|
0 |
不启用该组优化 |
2 |
DBLINK 相关子查询转换为函数,受 ENABLE_DBLINK_TO_INV 控制 |
4 |
多列 IN 转换为 EXISTS,受 MULTI_IN_CVT_EXISTS 控制 |
8 |
将引用列转换为变量 VAR |
16 |
使用临时函数替代查询项中的相关查询表达式,受 ENABLE_RQ_TO_INV 控制 |
32 |
存储过程或语句块中的多列表达式过滤条件含非相关子查询时转换为连接 |
前期使用值 0/1、0/4 和 0/8 时,当前版本的默认优化已经生成相同 SPL 或半连接计划,差异不明显。因此最终选用值 16,并配合 ENABLE_RQ_TO_INV 验证相关查询项能否转换成临时方法。
EXPLAIN
SELECT /*+ SUBQ_CVT_SPL_FLAG(0) ENABLE_RQ_TO_INV(0) */
A.ORDER_ID,
(
SELECT MAX(B.AMOUNT)
FROM T_ORDER_DETAIL B
WHERE B.ORDER_ID = A.ORDER_ID
) AS MAX_AMOUNT
FROM T_ORDER A
WHERE A.ORDER_ID <= 1000;
EXPLAIN
SELECT /*+ SUBQ_CVT_SPL_FLAG(16) ENABLE_RQ_TO_INV(1) */
A.ORDER_ID,
(
SELECT MAX(B.AMOUNT)
FROM T_ORDER_DETAIL B
WHERE B.ORDER_ID = A.ORDER_ID
) AS MAX_AMOUNT
FROM T_ORDER A
WHERE A.ORDER_ID <= 1000;
关闭临时函数转换时:
PIPE2
外层扫描 T_ORDER
SPL2 has_var(1)
AAGR2
IDX_DETAIL_ORDER
scan_range[var1,var1]
开启值 16 后,主计划中的 PIPE2 和 SPL2 消失,并额外生成:
METHOD: PHAF_xxx
NTTS2
AAGR2
IDX_DETAIL_ORDER
scan_range[exp45,exp45]
SUBQ_CVT_SPL_FLAG(16) 配合 ENABLE_RQ_TO_INV(1),把查询项中的相关标量子查询从主计划中的 SPL2 结构转换为临时方法调用。两种计划内部都使用 IDX_DETAIL_ORDER,但相关表达式的组织和调用方式已经发生明显变化。
OPTIMIZER_OR_NBEXP该参数控制 OR 布尔表达式的整体处理、展开、范围合并等优化。
| 值 | 含义 |
|---|---|
0 |
不启用该组 OR 优化 |
1 |
生成 UNION_FOR_OR 时使用无 KEY 比较方式 |
2 |
OR 表达式优先整体处理 |
4 |
相关子查询中的 OR 也优先整体处理 |
8 |
合并 OR 布尔表达式中的重叠或连续范围 |
16 |
优化同一列上的范围条件与 IS NULL 组合 |
32 |
OR 公因子包含子查询时使用 UNION_OR,便于过滤下放 |
64 |
屏蔽 UNION_OR 移除 AUTOID 的优化,主要作为保险控制位,通常不建议开启 |
构造两个重叠范围:
100~300
OR
200~500
逻辑上可合并为一个连续范围 100~500。先确认实际返回 4010 行:
SELECT COUNT(*) AS CNT
FROM T_ORDER A
WHERE A.SCORE BETWEEN 100 AND 300
OR A.SCORE BETWEEN 200 AND 500;
再比较值 0 和值 8:
EXPLAIN
SELECT /*+ OPTIMIZER_OR_NBEXP(0)
INDEX(A, IDX_ORDER_SCORE) */
A.ORDER_ID,
A.SCORE
FROM T_ORDER A
WHERE A.SCORE BETWEEN 100 AND 300
OR A.SCORE BETWEEN 200 AND 500;
EXPLAIN
SELECT /*+ OPTIMIZER_OR_NBEXP(8)
INDEX(A, IDX_ORDER_SCORE) */
A.ORDER_ID,
A.SCORE
FROM T_ORDER A
WHERE A.SCORE BETWEEN 100 AND 300
OR A.SCORE BETWEEN 200 AND 500;
值 0:
SSCN IDX_ORDER_SCORE
↓
SLCT2 保留原始 OR 条件
↓
估算 975 行
值 8:
SSEK2 IDX_ORDER_SCORE
scan_range[100,500]
估算 4010 行
开启范围合并后,两个重叠 OR 分支被直接整理成一个索引扫描范围,SLCT2 的 OR 过滤节点消失,估算行数也由 975 修正为与实际结果一致的 4010。
TOP_ORDER_OPT_FLAG该参数控制 TOP + ORDER BY 查询的优化,目标通常是利用与排序列一致的索引,尽量减少或移除 SORT。
| 值 | 含义 |
|---|---|
0 |
不启用该组 TOP ORDER 优化 |
1 |
对最优索引进行 TOP ORDER 优化 |
2 |
优先选择与排序列一致、可以消除排序的索引 |
4 |
查询含 TOP 和集函数但没有 GROUP BY 时,满足条件可移除 TOP |
8 |
已有 CSCN 计划时,根据预计回表行数判断是否继续使用 TOP ORDER 优化 |
16 |
没有去重操作时,使用堆排序算法优化 TOP N |
32 |
ORDER 所需 TOP 数大于 SELECT 估算行数时,使用目标索引全表行数计算代价 |
64 |
消除排序时,如果 ORDER 需要的孩子行数小于 TOP 行数,优先考虑不消除排序的计划 |
比较值 0 和值 2 对排序列索引选择与扫描行数估算的影响。
SELECT MIN(SCORE),
MAX(SCORE),
COUNT(*)
FROM T_ORDER;
EXPLAIN
SELECT /*+ TOP_ORDER_OPT_FLAG(0) */
TOP 10
A.ORDER_ID,
A.SCORE
FROM T_ORDER A
ORDER BY A.SCORE;
EXPLAIN
SELECT /*+ TOP_ORDER_OPT_FLAG(2) */
TOP 10
A.ORDER_ID,
A.SCORE
FROM T_ORDER A
ORDER BY A.SCORE;
两份计划都使用:
TOPN2
BLKUP2
SSCN IDX_ORDER_SCORE
两边都没有显式 SORT,说明即使参数值为 0,优化器仍可根据普通代价选择已经有序的分数索引。区别主要体现在估算:
值 0:索引扫描估算 10000 行,代价 2
值 2:索引扫描估算 256 行,代价 1
本实验没有得到“值 0 使用 SORT、值 2 不使用 SORT”的绝对差异,而是体现值 2 对排序索引的优先倾向和 TOP 场景估算方式。参数实验应以实际计划为准,不能仅根据参数名称预设结果。
ENABLE_INDEX_FILTER该参数控制过滤条件能否在索引访问阶段提前计算。
| 值 | 含义 |
|---|---|
0 |
不启用索引过滤 |
1 |
如果过滤列包含在索引中,在 SSEK2 后、回表前执行过滤,减少中间结果 |
2 |
在值 1 的基础上,将 IN 查询列表转换为 HASH RIGHT SEMI JOIN;值 2 与值 3 效果相同 |
4 |
对会重复执行且元素较多的 IN LIST,不把它作为索引过滤条件 |
使用复合索引 IDX_ORDER_STATUS_AMOUNT(STATUS, ORDER_AMOUNT),让 STATUS 负责范围访问,ORDER_AMOUNT 负责进一步过滤,观察过滤发生在回表前还是回表后。
EXPLAIN
SELECT /*+ ENABLE_INDEX_FILTER(0)
INDEX(A, IDX_ORDER_STATUS_AMOUNT) */
A.ORDER_ID,
A.STATUS,
A.ORDER_AMOUNT
FROM T_ORDER A
WHERE A.STATUS BETWEEN 1 AND 5
AND A.ORDER_AMOUNT >= 9000;
EXPLAIN
SELECT /*+ ENABLE_INDEX_FILTER(1)
INDEX(A, IDX_ORDER_STATUS_AMOUNT) */
A.ORDER_ID,
A.STATUS,
A.ORDER_AMOUNT
FROM T_ORDER A
WHERE A.STATUS BETWEEN 1 AND 5
AND A.ORDER_AMOUNT >= 9000;
值 0:
SSEK2:索引范围得到 3500 行
↓
BLKUP2:3500 行回表
↓
SLCT2:过滤到 349 行
值 1:
SSEK2
↓
SLCT2:先过滤到 349 行
↓
BLKUP2:仅对 349 行回表
计划层级可以直接看出:值 0 的 SLCT2 位于 BLKUP2 上方,值 1 的 SLCT2 位于 BLKUP2 下方。
开启索引过滤后,ORDER_AMOUNT >= 9000 在索引访问阶段提前计算,把需要回表的数据从约 3500 行减少到 349 行,估算代价由 3 降为 1。
ENABLE_RQ_TO_NONREF_SPL该参数用于调整相关查询表达式的处理方式,使相关查询在满足条件时从平坦化连接处理转为 SPL 逐行处理。
| 值 | 含义 |
|---|---|
0 |
不启用该优化 |
1 |
优化查询项中出现的相关子查询表达式 |
2 |
优化查询项和 WHERE 中出现的相关子查询表达式 |
4 |
相关查询使用 SPL 去相关后,可以作为单表过滤条件 |
查询前 100 个订单,并在查询项中计算每个订单明细的最大金额:
EXPLAIN
SELECT /*+ ENABLE_RQ_TO_NONREF_SPL(0) */
TOP 100
A.ORDER_ID,
(
SELECT MAX(B.AMOUNT)
FROM T_ORDER_DETAIL B
WHERE B.ORDER_ID = A.ORDER_ID
) AS MAX_AMOUNT
FROM T_ORDER A;
EXPLAIN
SELECT /*+ ENABLE_RQ_TO_NONREF_SPL(1) */
TOP 100
A.ORDER_ID,
(
SELECT MAX(B.AMOUNT)
FROM T_ORDER_DETAIL B
WHERE B.ORDER_ID = A.ORDER_ID
) AS MAX_AMOUNT
FROM T_ORDER A;
值 0:
SPL2 has_var(0)
HAGR2
HASH LEFT JOIN2
T_ORDER 全扫描 10000 行
T_ORDER_DETAIL 全扫描 30000 行
值 1:
SPL2 has_var(1)
AAGR2
IDX_DETAIL_ORDER
scan_range[var1,var1]
值 0 采用全量连接后分组的方式;值 1 则在取得 TOP 100 个订单后,将当前 ORDER_ID 作为变量传给子查询,并通过明细索引查询约 3 行。
当前场景中,值 1 把相关查询转换为逐订单索引探测方式,估算代价由 11 降至 1。该差异也说明,相关子查询是否适合平坦化,和外层返回行数、内表索引以及数据规模密切相关。
BEXP_CALC_ST_FLAG该参数用于控制优化器在处理过滤条件时,是否根据统计信息修正选择率估算。
本文重点对比值 1 和值 128:
| 值 | 含义 |
|---|---|
1 |
启用同表不同列比较场景下的选择率调整,本实验中不针对绑定参数值使用列统计信息修正 |
128 |
列与参数类型值比较时,根据统计信息修正选择率 |
本实验使用绑定参数:
WHERE B = ?
其中 ? 的具体值在 SQL 执行时传入。为避免复用缓存计划,SQL 中同时使用 PLAN_NO_CACHE。
构造数据分布明显倾斜的测试表:
DROP TABLE AA;
CREATE TABLE AA AS
SELECT LEVEL AS A,
'1'::INT AS B
FROM DUAL
CONNECT BY LEVEL < 1000;
INSERT INTO AA
SELECT LEVEL AS A,
LEVEL AS B
FROM DUAL
CONNECT BY LEVEL < 100;
COMMIT;
CREATE INDEX TEST01 ON AA(B);
最终数据分布为:
B=1:1000 行
B=2~99:每个值 1 行
为了使优化器能够识别 B 列的数据倾斜,需要收集相应的列统计信息。
开启实际执行计划跟踪:
SET AUTOTRACE TRACE;
先执行值 1:
SELECT /*+ PLAN_NO_CACHE BEXP_CALC_ST_FLAG(1) */
*
FROM AA
WHERE B = ?;
绑定参数传入:
1
再执行值 128:
SELECT /*+ PLAN_NO_CACHE BEXP_CALC_ST_FLAG(128) */
*
FROM AA
WHERE B = ?;
绑定参数仍传入:
1
两次查询实际都返回 1000 行。
值 1 时,执行计划为:
BLKUP2 TEST01
SSEK2 TEST01
估算返回 11 行,但实际返回 1000 行。优化器仍选择通过 TEST01 索引查找,说明该设置下没有根据绑定值 1 的高频分布修正选择率。
值 128 时,执行计划变为:
SLCT2
CSCN2 AA
估算返回 1000 行,与实际返回行数一致。优化器识别出绑定值 1 在 B 列中属于高频值,因此放弃索引访问,改为全表扫描。
在相同绑定值 1 下,两种设置生成了不同的估算和访问路径:
BEXP_CALC_ST_FLAG(1)
估算 11 行
使用 TEST01 索引
实际返回 1000 行
BEXP_CALC_ST_FLAG(128)
估算 1000 行
使用 CSCN2 全表扫描
实际返回 1000 行
BEXP_CALC_ST_FLAG(128) 能够结合绑定参数和列统计信息修正选择率。由于参数值 1 实际匹配大部分数据,优化器将估算行数修正为 1000,并选择更适合大量数据访问的全表扫描计划。
该参数主要影响的是优化器的选择率估算,并可能进一步改变索引扫描或全表扫描等访问路径。最终计划仍会受到数据规模、统计信息和索引代价等因素影响。
实验完成后关闭自动跟踪:
VIEW_PULLUP_FLAG该参数控制是否将视图展开为原始定义,使外层查询和视图内部关系进一步合并。
| 值 | 含义 |
|---|---|
0 |
不进行视图上拉 |
1 |
上拉不包含别名和同名列的视图 |
2 |
包含别名和同名列的视图也允许上拉 |
4 |
强制允许带变量的查询进行视图上拉,可能存在结果风险,应谨慎使用 |
8 |
不上拉 LEFT JOIN 右孩子、RIGHT JOIN 左孩子及 FULL JOIN 两侧孩子 |
16 |
不上拉 LEFT JOIN 左孩子 |
32 |
上拉后从视图符号表删除未被引用的列 |
创建带别名列的视图:
CREATE VIEW V_ORDER_CUSTOMER AS
SELECT A.ORDER_ID,
A.CUSTOMER_ID,
A.STATUS,
C.CUSTOMER_ID AS CUSTOMER_ID2,
C.CUSTOMER_LEVEL
FROM T_ORDER A
JOIN T_CUSTOMER C
ON A.CUSTOMER_ID = C.CUSTOMER_ID;
比较值 0 与值 2 对视图层级的影响:
EXPLAIN
SELECT /*+ VIEW_PULLUP_FLAG(0) */
V.ORDER_ID,
V.CUSTOMER_ID
FROM V_ORDER_CUSTOMER V
WHERE V.CUSTOMER_LEVEL = 5
AND V.STATUS = 1;
EXPLAIN
SELECT /*+ VIEW_PULLUP_FLAG(2) */
V.ORDER_ID,
V.CUSTOMER_ID
FROM V_ORDER_CUSTOMER V
WHERE V.CUSTOMER_LEVEL = 5
AND V.STATUS = 1;
值 0:
PRJT2
PRJT2
HASH2 INNER JOIN
值 2:
PRJT2
HASH2 INNER JOIN
两份计划都已经将过滤条件作用到基表索引:
T_CUSTOMER:IDX_CUSTOMER_LEVEL
T_ORDER:IDX_ORDER_STATUS
区别是值 2 进一步消除了一个中间 PRJT2 投影层。
这个实验的差异没有索引或连接方式切换那么大,但仍能证明值 2 让带别名列的视图与外层查询进一步合并,计划结构更精简。估算行数和代价相同,因此不能仅凭节点更少断言实际运行一定更快。
TOP_DIS_HASH_FLAG该参数用于在 TOP 查询中限制或降低 HASH JOIN 的选择倾向。
| 值 | 含义 |
|---|---|
0 |
不进行该优化 |
1 |
OPTIMIZER_MODE=0 时禁用 TOP 下方 HASH JOIN;OPTIMIZER_MODE=1 时,TOP 下方最近的连接倾向于不使用 HASH JOIN |
2 |
OPTIMIZER_MODE=0 时禁用 TOP 下方 HASH JOIN;OPTIMIZER_MODE=1 时,TOP 下方所有连接都倾向于不使用 HASH JOIN |
查询订单和明细连接后的前 10 行,比较值 0 和值 1:
EXPLAIN
SELECT /*+ TOP_DIS_HASH_FLAG(0) */
TOP 10
A.ORDER_ID,
B.AMOUNT
FROM T_ORDER A
JOIN T_ORDER_DETAIL B
ON B.ORDER_ID = A.ORDER_ID
WHERE A.STATUS = 1;
EXPLAIN
SELECT /*+ TOP_DIS_HASH_FLAG(1) */
TOP 10
A.ORDER_ID,
B.AMOUNT
FROM T_ORDER A
JOIN T_ORDER_DETAIL B
ON B.ORDER_ID = A.ORDER_ID
WHERE A.STATUS = 1;
值 0:
TOPN2
HASH2 INNER JOIN
A:IDX_ORDER_STATUS
B:CSCN2 全扫描 30000 行
值 1:
TOPN2
HASH2 INNER JOIN
NEST LOOP INDEX JOIN2
ACTRL
A:IDX_ORDER_STATUS
B:IDX_DETAIL_ORDER
B:CSCN2
值 1 后最上层仍然能看到 HASH2,但计划增加了 ACTRL + NEST LOOP INDEX JOIN2 候选路径,估算连接行数由 30000 降为 1000,总体代价由 7 降为 1。
在当前新优化器模式下,值 1 的含义是“倾向于不使用 HASH JOIN”,而不是保证计划中完全没有 HASH。优化器生成了更适合 TOP 快速返回少量结果的嵌套循环索引候选,同时保留 HASH 路径作为自适应选择。
直接写在 SQL 中的 Hint 需要修改应用语句。如果应用代码暂时无法修改,达梦还支持通过 SF_INJECT_HINT 将 Hint 规则绑定到指定 SQL。
注入流程如下:
开启 ENABLE_INJECT_HINT
↓
查看原 SQL 默认计划
↓
使用 SF_INJECT_HINT 创建规则
↓
原 SQL 不添加 Hint 再次执行
↓
对比计划是否发生变化
SP_SET_PARA_VALUE(1, 'ENABLE_INJECT_HINT', 1);
SELECT PARA_NAME,
PARA_VALUE
FROM V$DM_INI
WHERE PARA_NAME = 'ENABLE_INJECT_HINT';
预期参数值为 1。
SF_DEINJECT_HINT('TEST_ORDER_NL');
如果规则不存在会报提示,确认没有同名规则后继续即可。
EXPLAIN SELECT A.ORDER_ID, B.DETAIL_ID FROM T_ORDER A, T_ORDER_DETAIL B WHERE A.ORDER_ID=B.ORDER_ID AND A.STATUS=1;
默认计划为:
HASH2 INNER JOIN
A:IDX_ORDER_STATUS
B:CSCN2 全扫描 T_ORDER_DETAIL
USE_NLSF_INJECT_HINT(
'SELECT A.ORDER_ID, B.DETAIL_ID FROM T_ORDER A, T_ORDER_DETAIL B WHERE A.ORDER_ID=B.ORDER_ID AND A.STATUS=1;',
'USE_NL(A,B)',
'TEST_ORDER_NL',
'测试不修改SQL时自动应用USE_NL',
TRUE,
FALSE,
TRUE
);
后三个参数含义:
TRUE :规则启用
FALSE :精确匹配
TRUE :同步清理缓存计划
查看规则:
SELECT NAME,
VALIDATE,
SQL_TEXT,
HINT_TEXT
FROM SYSINJECTHINT
WHERE NAME = 'TEST_ORDER_NL';
应看到:
NAME TEST_ORDER_NL
VALIDATE TRUE
HINT_TEXT USE_NL(A,B)
SQL 本身仍然没有写 /*+ USE_NL */:
EXPLAIN SELECT A.ORDER_ID, B.DETAIL_ID FROM T_ORDER A, T_ORDER_DETAIL B WHERE A.ORDER_ID=B.ORDER_ID AND A.STATUS=1;
计划变为:
NEST LOOP INDEX JOIN2
A:IDX_ORDER_STATUS
B:IDX_DETAIL_ORDER
scan_range[A.ORDER_ID,A.ORDER_ID]
也就是:
注入前:HASH2 INNER JOIN + 明细表全扫描
注入后:NEST LOOP INDEX JOIN2 + 明细表索引探测
这证明注入规则已经命中,应用 SQL 无需增加 Hint,优化器仍自动采用 USE_NL(A,B)。
SF_DEINJECT_HINT('TEST_ORDER_NL', TRUE);
Hint 注入规则是全局对象,生产环境使用时必须建立变更、验证和回退流程,避免规则长期遗留后影响其他会话。
本实验还发现,精确匹配规则对 SQL 文本匹配要求严格。为了避免规则已创建但没有命中,建议:
SYSINJECTHINT 检查规则,并通过实际计划变化确认是否真正生效。本文通过执行计划对比,验证了以下 Hint 和优化参数在当前实验环境中的作用:
INDEX / NO_INDEX:影响索引访问路径的选择;USE_HASH / USE_NL:影响表连接方式;ENABLE_IN_VALUE_LIST_OPT:影响 IN 列表的内部转换和索引访问方式;SUBQ_CVT_SPL_FLAG:影响相关子查询的实现方式;OPTIMIZER_OR_NBEXP:影响 OR 表达式的范围合并;TOP_ORDER_OPT_FLAG:影响 TOP 与 ORDER BY 场景下的索引选择和代价估算;ENABLE_INDEX_FILTER:影响过滤条件在回表前或回表后执行;ENABLE_RQ_TO_NONREF_SPL:影响相关子查询采用批量改写还是逐行处理;BEXP_CALC_ST_FLAG:影响特定过滤条件的选择率估算;VIEW_PULLUP_FLAG:影响视图与外层查询的合并程度;TOP_DIS_HASH_FLAG:影响 TOP 查询中 HASH JOIN 的选择倾向;以上实验只说明这些参数在本文 SQL、数据分布和数据库版本下产生的计划变化。实际使用时仍应结合统计信息、数据规模、实际执行时间和并发环境进行验证。
本文参数含义和 Hint 注入语法主要参考以下达梦官方资料:
SF_INJECT_HINT、SF_DEINJECT_HINT 使用说明。参数默认值和具体控制位可能随数据库版本变化。实际使用时,应以目标环境中的以下查询结果为准:
SELECT PARA_NAME,
PARA_VALUE
FROM V$DM_INI
WHERE PARA_NAME = '参数名';
SELECT PARA_NAME,
HINT_TYPE
FROM V$HINT_INI_INFO
WHERE PARA_NAME = '参数名';
文章
阅读量
获赞
