达梦数据库 Hint 使用与 Hint 注入实战

本文通过一组可复现的执行计划实验,介绍达梦数据库 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 不只有 INDEXUSE_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 时要注意三点:

  1. Hint 是对优化器的干预,不代表一定比默认计划更好;
  2. 指定的访问路径如果无法实现,Hint 可能不会得到预期计划;
  3. 必须通过 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 INDEXNO_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_HASHUSE_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/10/40/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 后,主计划中的 PIPE2SPL2 消失,并额外生成:

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 行回表

计划层级可以直接看出:值 0SLCT2 位于 BLKUP2 上方,值 1SLCT2 位于 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 文本匹配要求严格。为了避免规则已创建但没有命中,建议:

  1. 注入和执行时使用同一份完整 SQL 文本;
  2. 明确是否保留末尾分号;
  3. 创建或修改规则后清理相关缓存计划;
  4. 通过 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 注入语法主要参考以下达梦官方资料:

  1. 《DM 物理存储结构》中的查询相关 INI 参数说明;
  2. 《性能优化》中的 SQL 和参数优化建议;
  3. 《SQL 调优》中的优化器提示说明;
  4. 《数据查询语句》中的 HINT 子句说明;
  5. 《附录》中的 SF_INJECT_HINTSF_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 = '参数名';
相关推荐
Gauss松鼠会3 小时前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结
2601_965798474 小时前
How to Build a Custom Artisan Store on WordPress: Crafti Theme Review
linux·服务器·数据库
霸道流氓气质4 小时前
Spring 事务传播机制与 REQUIRES_NEW
java·数据库·spring
YOU OU4 小时前
Redis分布式锁
数据库·redis·分布式
小草儿7995 小时前
GBase 8s CLOB → VARCHAR(32739) 数据迁移方案文档
数据库
秋田君5 小时前
QT_QColorDialog颜色对话框
java·数据库·qt
涛声依旧393165 小时前
运维项目:InnoAI SQL 助手
运维·数据库·sql
万悉科技5 小时前
时尚品牌GEO技术选型指南:LLM原生优化与AI记忆构建全解析
数据库·人工智能·搜索引擎
lb29175 小时前
在window上安装orcale数据库流程(快速,简洁)
数据库