本文通过一组可复现的执行计划实验,介绍达梦数据库 Hint 的基本用法,并重点验证 9 个优化参数在不同取值下对执行计划的影响。最后再演示如何通过 Hint 注入,在不修改应用 SQL 的情况下改变执行计划。
text
原始 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 是什么
Hint 是写在 SQL 中的优化器提示,用来影响优化器的计划生成过程。常见写法如下:
sql
SELECT /*+ HINT_NAME(arguments) */
column_list
FROM table_name
WHERE condition;
一条 SQL 可以同时包含多个 Hint:
sql
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:
sql
SELECT PARA_NAME,
HINT_TYPE
FROM V$HINT_INI_INFO
WHERE PARA_NAME = 'ENABLE_IN_VALUE_LIST_OPT';
HINT_TYPE 常见值包括:
OPT:在优化分析阶段生效,主要影响执行计划生成;EXEC:在运行阶段生效,通常要求写在最外层 SQL 中。
还可以查看当前参数值:
sql
SELECT PARA_NAME,
PARA_VALUE
FROM V$DM_INI
WHERE PARA_NAME = 'ENABLE_IN_VALUE_LIST_OPT';
使用 Hint 时要注意三点:
- Hint 是对优化器的干预,不代表一定比默认计划更好;
- 指定的访问路径如果无法实现,Hint 可能不会得到预期计划;
- 必须通过
EXPLAIN检查 Hint 是否真正生效,并在真实数据量和并发条件下验证性能。
二、实验环境与测试数据
本次实验环境:
text
数据库:DM Database Server x64 V8
版本标识:--03134284488-20260212-314192-20200
测试用户:SYSDBA
测试工具:disql
实验使用三张表:
text
T_CUSTOMER 1000 行
T_ORDER 10000 行
T_ORDER_DETAIL 30000 行
主要数据分布:
text
T_ORDER_DETAIL:每个订单对应 3 条明细
T_ORDER.SCORE:0~999,每个值约 10 行
T_ORDER.TEST_FLAG:值 1 约 9000 行,值 9 约 1000 行
T_ORDER.STATUS:存在明显分布差异,便于构造索引和连接实验
实验中使用的主要索引:
sql
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);
2.1 本文常见执行计划操作符
| 操作符 | 本文中的简化理解 |
|---|---|
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 |
查询项投影处理 |
三、先做三个基础 Hint 实验
在进入 9 个参数前,先用最直观的索引和连接 Hint 观察优化器计划如何变化。
3.1 INDEX 与 NO_INDEX:控制索引访问
INDEX 用于提示优化器使用指定索引,NO_INDEX 用于禁止指定索引。
实验 SQL:
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 计划都使用:
text
SSEK2 IDX_ORDER_STATUS
↓
BLKUP2 回表
使用 NO_INDEX 后变为:
text
CSCN2 全扫描
↓
SLCT2 过滤 STATUS=1
这说明 Hint 可以直接干预表的访问路径。不过默认计划本身已经选择了正确索引,因此显式 INDEX 并没有进一步改变结构;真正明显的变化来自 NO_INDEX。
3.2 USE_HASH 与 USE_NL:控制连接方式
测试 SQL 连接订单表和订单明细表:
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 后,计划固定为:
text
HASH2 INNER JOIN
A:IDX_ORDER_STATUS
B:CSCN2 全扫描明细表
使用 USE_NL 后固定为:
text
NEST LOOP INDEX JOIN2
A:IDX_ORDER_STATUS
B:IDX_DETAIL_ORDER
对于过滤后外表行数较少、内表连接列有索引的场景,嵌套循环可以按外层订单号逐次探测明细索引;HASH JOIN 则更偏向先扫描并构建哈希结构。


3.3 OR 与 IN:同一逻辑的不同写法
先比较两种等价写法:
sql
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 最终都生成:
text
NEST LOOP INDEX JOIN2
├── CONST VALUE LIST:1、2、3
└── IDX_ORDER_STATUS 索引探测
说明优化器已经把多个同列等值 OR 统一处理成常量值列表,再对索引逐值探测。因此,SQL 写成 OR 或 IN 并不一定产生不同计划,最终还要看优化器参数和改写规则。
四、9 个优化参数 Hint 实验
下面每个实验都先介绍参数含义和取值,再给出实验 SQL、计划变化和结论。位标志型参数允许将多个值相加组合使用。
4.1 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) 在关闭和开启语义阶段常量列表转换时,执行计划如何变化。
sql
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:
text
CSCN2 扫描 10000 行
↓
SLCT2 判断 STATUS IN LIST
↓
估算返回 2500 行
值 1:
text
CONST VALUE LIST:1、2、3
↓
NEST LOOP INDEX JOIN2
↓
IDX_ORDER_STATUS 逐值查找
实验结论
ENABLE_IN_VALUE_LIST_OPT(1) 将 IN 列表转换为内部常量值集合,并以列表中的值驱动状态索引访问;值 0 则保留普通过滤结构,先扫描再判断 IN 条件。
4.2 SUBQ_CVT_SPL_FLAG
参数作用
该参数控制相关子查询的多种实现和改写方式。
| 值 | 含义 |
|---|---|
0 |
不启用该组优化 |
1 |
使用 SPL2 实现相关子查询,受 ENABLE_RQ_TO_SPL 控制 |
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 验证相关查询项能否转换成临时方法。
sql
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;
计划变化
关闭临时函数转换时:
text
PIPE2
外层扫描 T_ORDER
SPL2 has_var(1)
AAGR2
IDX_DETAIL_ORDER
scan_range[var1,var1]
开启值 16 后,主计划中的 PIPE2 和 SPL2 消失,并额外生成:
text
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,但相关表达式的组织和调用方式已经发生明显变化。

4.3 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 的优化,主要作为保险控制位,通常不建议开启 |
实验目的
构造两个重叠范围:
text
100~300
OR
200~500
逻辑上可合并为一个连续范围 100~500。先确认实际返回 4010 行:
sql
SELECT COUNT(*) AS CNT
FROM T_ORDER A
WHERE A.SCORE BETWEEN 100 AND 300
OR A.SCORE BETWEEN 200 AND 500;
再比较值 0 和值 8:
sql
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:
text
SSCN IDX_ORDER_SCORE
↓
SLCT2 保留原始 OR 条件
↓
估算 975 行
值 8:
text
SSEK2 IDX_ORDER_SCORE
scan_range[100,500]
估算 4010 行
实验结论
开启范围合并后,两个重叠 OR 分支被直接整理成一个索引扫描范围,SLCT2 的 OR 过滤节点消失,估算行数也由 975 修正为与实际结果一致的 4010。

4.4 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 对排序列索引选择与扫描行数估算的影响。
sql
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;
计划变化
两份计划都使用:
text
TOPN2
BLKUP2
SSCN IDX_ORDER_SCORE
两边都没有显式 SORT,说明即使参数值为 0,优化器仍可根据普通代价选择已经有序的分数索引。区别主要体现在估算:
text
值 0:索引扫描估算 10000 行,代价 2
值 2:索引扫描估算 256 行,代价 1
实验结论
本实验没有得到"值 0 使用 SORT、值 2 不使用 SORT"的绝对差异,而是体现值 2 对排序索引的优先倾向和 TOP 场景估算方式。参数实验应以实际计划为准,不能仅根据参数名称预设结果。
4.5 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 负责进一步过滤,观察过滤发生在回表前还是回表后。
sql
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:
text
SSEK2:索引范围得到 3500 行
↓
BLKUP2:3500 行回表
↓
SLCT2:过滤到 349 行
值 1:
text
SSEK2
↓
SLCT2:先过滤到 349 行
↓
BLKUP2:仅对 349 行回表
计划层级可以直接看出:值 0 的 SLCT2 位于 BLKUP2 上方,值 1 的 SLCT2 位于 BLKUP2 下方。
实验结论
开启索引过滤后,ORDER_AMOUNT >= 9000 在索引访问阶段提前计算,把需要回表的数据从约 3500 行减少到 349 行,估算代价由 3 降为 1。


4.6 ENABLE_RQ_TO_NONREF_SPL
参数作用
该参数用于调整相关查询表达式的处理方式,使相关查询在满足条件时从平坦化连接处理转为 SPL 逐行处理。
| 值 | 含义 |
|---|---|
0 |
不启用该优化 |
1 |
优化查询项中出现的相关子查询表达式 |
2 |
优化查询项和 WHERE 中出现的相关子查询表达式 |
4 |
相关查询使用 SPL 去相关后,可以作为单表过滤条件 |
实验目的
查询前 100 个订单,并在查询项中计算每个订单明细的最大金额:
sql
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:
text
SPL2 has_var(0)
HAGR2
HASH LEFT JOIN2
T_ORDER 全扫描 10000 行
T_ORDER_DETAIL 全扫描 30000 行
值 1:
text
SPL2 has_var(1)
AAGR2
IDX_DETAIL_ORDER
scan_range[var1,var1]
值 0 采用全量连接后分组的方式;值 1 则在取得 TOP 100 个订单后,将当前 ORDER_ID 作为变量传给子查询,并通过明细索引查询约 3 行。
实验结论
当前场景中,值 1 把相关查询转换为逐订单索引探测方式,估算代价由 11 降至 1。该差异也说明,相关子查询是否适合平坦化,和外层返回行数、内表索引以及数据规模密切相关。
4.7 BEXP_CALC_ST_FLAG
参数作用
该参数控制哪些过滤条件的选择率可以依据动态采样或统计信息进行调整。
| 值 | 含义 |
|---|---|
0 |
使用默认策略,不额外调整 |
1 |
同一表不同列之间比较时调整 |
2 |
过滤条件包含函数时调整 |
4 |
同一表 AND/OR 过滤条件时调整 |
8 |
对 CONTAINS 过滤条件调整 |
16 |
对单表过滤条件调整 |
32 |
对 LIKE 过滤条件调整 |
64 |
列与参数类型值比较时,依据动态采样调整 |
128 |
列与参数类型值比较时,依据统计信息调整 |
实验目的
尝试使用算术表达式构造无法直接作为索引范围的过滤条件,比较值 0 和值 128 是否改变估算行数或访问方式。
sql
EXPLAIN
SELECT /*+ BEXP_CALC_ST_FLAG(0) */
ORDER_ID,
SCORE
FROM T_ORDER
WHERE SCORE + 0 BETWEEN 100 AND 110;
EXPLAIN
SELECT /*+ BEXP_CALC_ST_FLAG(128) */
ORDER_ID,
SCORE
FROM T_ORDER
WHERE SCORE + 0 BETWEEN 100 AND 110;
实验结果
两份计划完全一致:
text
CSCN2 扫描 10000 行
↓
SLCT2 过滤 SCORE+0 BETWEEN 100 AND 110
↓
估算返回 25 行
实验结论
本实验没有观察到值 0 和值 128 的差异。原因是值 128 的典型作用对象是"列与参数类型值"的比较,而本文使用的是 disql EXPLAIN 中的常量表达式,并不是真正的预编译绑定参数。
因此,这个结果不能解释为参数无效,只能说明当前 SQL 没有触发它的目标场景。若要进一步验证,通常需要准备倾斜数据、统计信息,并通过应用接口以真实绑定变量执行 SQL,再比较不同绑定值下的选择率和访问路径。

4.8 VIEW_PULLUP_FLAG
参数作用
该参数控制是否将视图展开为原始定义,使外层查询和视图内部关系进一步合并。
| 值 | 含义 |
|---|---|
0 |
不进行视图上拉 |
1 |
上拉不包含别名和同名列的视图 |
2 |
包含别名和同名列的视图也允许上拉 |
4 |
强制允许带变量的查询进行视图上拉,可能存在结果风险,应谨慎使用 |
8 |
不上拉 LEFT JOIN 右孩子、RIGHT JOIN 左孩子及 FULL JOIN 两侧孩子 |
16 |
不上拉 LEFT JOIN 左孩子 |
32 |
上拉后从视图符号表删除未被引用的列 |
实验准备
创建带别名列的视图:
sql
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 对视图层级的影响:
sql
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:
text
PRJT2
PRJT2
HASH2 INNER JOIN
值 2:
text
PRJT2
HASH2 INNER JOIN
两份计划都已经将过滤条件作用到基表索引:
text
T_CUSTOMER:IDX_CUSTOMER_LEVEL
T_ORDER:IDX_ORDER_STATUS
区别是值 2 进一步消除了一个中间 PRJT2 投影层。
实验结论
这个实验的差异没有索引或连接方式切换那么大,但仍能证明值 2 让带别名列的视图与外层查询进一步合并,计划结构更精简。估算行数和代价相同,因此不能仅凭节点更少断言实际运行一定更快。

4.9 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:
sql
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:
text
TOPN2
HASH2 INNER JOIN
A:IDX_ORDER_STATUS
B:CSCN2 全扫描 30000 行
值 1:
text
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 路径作为自适应选择。

五、Hint 注入:不修改原 SQL 也能改变计划
直接写在 SQL 中的 Hint 需要修改应用语句。如果应用代码暂时无法修改,达梦还支持通过 SF_INJECT_HINT 将 Hint 规则绑定到指定 SQL。
注入流程如下:
text
开启 ENABLE_INJECT_HINT
↓
查看原 SQL 默认计划
↓
使用 SF_INJECT_HINT 创建规则
↓
原 SQL 不添加 Hint 再次执行
↓
对比计划是否发生变化
5.1 开启 Hint 注入
sql
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。
5.2 删除旧规则
sql
SF_DEINJECT_HINT('TEST_ORDER_NL');
如果规则不存在会报提示,确认没有同名规则后继续即可。
5.3 查看注入前计划
sql
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;
默认计划为:
text
HASH2 INNER JOIN
A:IDX_ORDER_STATUS
B:CSCN2 全扫描 T_ORDER_DETAIL
5.4 精确注入 USE_NL
sql
SF_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
);
后三个参数含义:
text
TRUE :规则启用
FALSE :精确匹配
TRUE :同步清理缓存计划
查看规则:
sql
SELECT NAME,
VALIDATE,
SQL_TEXT,
HINT_TEXT
FROM SYSINJECTHINT
WHERE NAME = 'TEST_ORDER_NL';
应看到:
text
NAME TEST_ORDER_NL
VALIDATE TRUE
HINT_TEXT USE_NL(A,B)

5.5 再次执行原 SQL
SQL 本身仍然没有写 /*+ USE_NL */:
sql
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;
计划变为:
text
NEST LOOP INDEX JOIN2
A:IDX_ORDER_STATUS
B:IDX_DETAIL_ORDER
scan_range[A.ORDER_ID,A.ORDER_ID]
也就是:
text
注入前:HASH2 INNER JOIN + 明细表全扫描
注入后:NEST LOOP INDEX JOIN2 + 明细表索引探测
这证明注入规则已经命中,应用 SQL 无需增加 Hint,优化器仍自动采用 USE_NL(A,B)。

5.6 实验结束后清理规则
sql
SF_DEINJECT_HINT('TEST_ORDER_NL', TRUE);
Hint 注入规则是全局对象,生产环境使用时必须建立变更、验证和回退流程,避免规则长期遗留后影响其他会话。
本实验还发现,精确匹配规则对 SQL 文本匹配要求严格。为了避免规则已创建但没有命中,建议:
- 注入和执行时使用同一份完整 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 的选择倾向;- Hint 注入:可以在不修改原 SQL 的情况下,为目标 SQL 自动应用 Hint。
以上实验只说明这些参数在本文 SQL、数据分布和数据库版本下产生的计划变化。实际使用时仍应结合统计信息、数据规模、实际执行时间和并发环境进行验证。
七、参考资料
本文参数含义和 Hint 注入语法主要参考以下达梦官方资料:
- 《DM 物理存储结构》中的查询相关 INI 参数说明;
- 《性能优化》中的 SQL 和参数优化建议;
- 《SQL 调优》中的优化器提示说明;
- 《数据查询语句》中的 HINT 子句说明;
- 《附录》中的
SF_INJECT_HINT、SF_DEINJECT_HINT使用说明。
参数默认值和具体控制位可能随数据库版本变化。实际使用时,应以目标环境中的以下查询结果为准:
sql
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 = '参数名';