#hint

hint::=

syntax
/*+ hint_name hint_intersperse hint_name hint_intersperse */

SQL在查询优化阶段,优化器根据内定规则确定执行计划;作为辅助手段, 开发者可以通过在语句中书写hint,指示优化器改变执行路径,以便快速的查询数据。

优化器可以根据系统统计信息动态调整执行计划,使用hint则对执行计划进行了唯一指定,所以应该被谨慎使用,只有在已获取到足够的统计信息,确认改变执行路径是对性能更优的方式,才建议给语句增加hint。当语句涉及的表结构、业务场景或数据量等信息发生变化时,应通过查看执行计划等手段,重新审视该hint是否合适。

hint有时候会失效,如在hint中指定了非法的顺序(OUTER JOIN则不允许指定顺序),或者指定了非法的JOIN类型等。正常情况下,优化器会优先选择有效的hint,即使该hint的代价更高,但是对于前述非法的hint,优化器会不选择该hint,并在执行计划中输出hint unused。

hint_name

hint提示项的名称,可以同时指定多项hint_name作为组合提示。

如下标识符将被作为YashanDB认可的hint提示项名称:

FULL INDEX NO_INDEX INDEX_FFS

PARALLEL

LEADING

NO_USE_HASH NO_USE_MERGE NO_USE_NL USE_HASH USE_MERGE USE_NL

SELECTIVITY

BULKLOAD

hint_intersperse

对于某些提示,需要提供更多的信息来结合指定,例如指定使用索引提示时的索引名称。

YashanDB中使用hint的规则如下:

  • 只有跟在SELECT、UPDATE、INSERT、MERGE或DELETE等关键字后面,且以”/*+ ” 作为开始( “+“ 号后面需有空格 ),“*/” 作为结束的语句块 ,才会被解析成hint。

  • 一个关键字后跟多组”/*+ */”时,只有第一组被解析成hint,其他忽略。

  • 一个hint里某项提示出现如下情况时,优化器忽略该项提示:

    • 拼写错误或语法错误
    • 不能识别的提示项名称
    • 与其他提示项相互冲突
    • hint_intersperse里包含了查询块
    • 在SQL语句中为表对象定义了别名,而提示中未使用别名

示例

--创建视图
CREATE OR REPLACE VIEW b AS 
SELECT * FROM branches WHERE branch_no = '0101'; 

--hint_intersperse里包含了查询块,该项提示被忽略
EXPLAIN SELECT /*+ FULL(b) */ *
FROM area, b
WHERE area.area_no = '04' 
AND area.area_no = b.area_no;

PLAN_DESCRIPTION                                                 
---------------------------------------------------------------- 
SQL hash value: 3312245078                                      
Optimizer: ADOPT_C                                              
                                                                
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
| Id | Operation type                 | Name                 | Owner      | Rows     | Cost(%CPU)  | Partition info                 |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
|  0 | SELECT STATEMENT               |                      |            |          |             |                                |
|* 1 |  MERGE JOIN                    |                      |            |       102|      296( 0)|                                |
|  2 |   MERGE SORT                   |                      |            |          |             |                                |
|  3 |    TABLE ACCESS BY INDEX ROWID | AREA                 | SALES      |          |             |                                |
|* 4 |     INDEX UNIQUE SCAN          | SYS_C_18             | SALES      |         1|      148( 0)|                                |
|  5 |   MERGE SORT                   |                      |            |          |             |                                |
|* 6 |    TABLE ACCESS BY INDEX ROWID | BRANCHES             | SALES      |          |             |                                |
|* 7 |     INDEX UNIQUE SCAN          | SYS_C_20             | SALES      |         1|      148( 0)|                                |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
                                                                
Operation Information (identified by operation id):             
---------------------------------------------------             
                                                                
   1 - Predicate : access( "AREA"."AREA_NO" = "BRANCHES"."AREA_NO" )
                   filter("AREA"."AREA_NO" = "BRANCHES"."AREA_NO")
   4 - Predicate : access("AREA"."AREA_NO" = '04')              
   6 - Predicate : filter('04' = "BRANCHES"."AREA_NO")          
   7 - Predicate : access("BRANCHES"."BRANCH_NO" = '0101')      
                                                                
Hint Information :                                              
---------------------------------------------------             
                                                                
FULL(b) / unresolved

--在branches表上新增一个索引
CREATE INDEX idx_branches_1 ON branches(area_no);

--提示项冲突
EXPLAIN SELECT /*+ FULL(b) INDEX(b IDX_BRANCHES_1) */ *
FROM branches b
WHERE b.branch_no='0401' AND b.area_no='04';

PLAN_DESCRIPTION                                                 
---------------------------------------------------------------- 
SQL hash value: 3081809351                                      
Optimizer: ADOPT_C                                              
                                                                
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
| Id | Operation type                 | Name                 | Owner      | Rows     | Cost(%CPU)  | Partition info                 |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
|  0 | SELECT STATEMENT               |                      |            |          |             |                                |
|* 1 |  TABLE ACCESS BY INDEX ROWID   | BRANCHES             | SALES      |          |             |                                |
|* 2 |   INDEX UNIQUE SCAN            | SYS_C_20             | SALES      |         1|      148( 0)|                                |
+----+--------------------------------+----------------------+------------+----------+-------------+--------------------------------+
                                                                
Operation Information (identified by operation id):             
---------------------------------------------------             
                                                                
   1 - Predicate : filter("B"."AREA_NO" = '04')                 
   2 - Predicate : access("B"."BRANCH_NO" = '0401')             
                                                                
Hint Information :                                              
---------------------------------------------------             
                                                                
FULL(b) / conflict with another                                 
INDEX(b idx_branches_1) / conflict with another        

# full提示项

使用FULL提示项时,需同时使用hint_intersperse指定表名,表示对该表执行全表扫描。

示例

SELECT /*+ FULL(a) */ * FROM area a WHERE a.area_no='02';

# index提示项

使用INDEX提示项时,需同时使用hint_intersperse指定表名 [索引名],表示对该表执行指定的索引扫描。

当hint_intersperse中包含多个索引时,优化器从中选择代价最低的索引;当hint_intersperse中未指定任何索引时,优化器从该表上所有的索引中选择代价最低的索引。

示例

--在branches表上新增一个索引
CREATE INDEX idx_branches_2 ON branches(area_no,branch_no);

--提示多个索引,或者不指定索引,优化器将选择代价最低的索引执行扫描
SELECT /*+ INDEX(b) */ * 
FROM branches b
WHERE b.branch_no LIKE '0%';

# index_ffs提示项

使用INDEX_FFS提示项时,需同时使用hint_intersperse指定表名 索引名,表示对该表执行指定的索引快速全表扫描。

只有当所有查询列都位于指定的索引列之中,即通过扫描索引就可以得到所有的查询列而不用回表时,优化器才会接受提示选择此索引。

示例

--按上面示例,branches上已存在idx_branches_2索引
SELECT /*+ INDEX_FFS(b idx_branches_2) */ b.area_no,b.branch_no 
FROM branches b
WHERE b.area_no='04' AND b.branch_no='0401';

# no_index提示项

使用NO_INDEX提示项时,需同时使用hint_intersperse指定表名 [索引名],表示不使用指定的索引对该表执行索引扫描。

当hint_intersperse中包含多个索引时,这些索引都将不会被优化器使用,但如果该表上存在其他索引,则优化器可能会选择那些索引(由优化器规则决定);当hint_intersperse中未指定任何索引时,则优化器将不会选择该表上的任何索引。

示例

SELECT /*+ NO_INDEX(b) */ * 
FROM branches b
WHERE b.branch_no LIKE '0%';

# parallel提示项

使用PARALLEL提示项时,需同时使用hint_intersperse指定表名和并行度 [表名,并行度],表示对指定表使用并行查询。

通过hint所指定的并行度最大为255,超过255时会退化为255。

无法跨select指定并行,此种场景请使用数据库参数DEGREE_OF_PARALLEL来指定。

对并行查询的指定只作为一个参考项,如果优化器评估后认为并行查询不是最优计划,则不会选择生成并行计划。

示例

SELECT /*+PARALLEL(t1,4)*/ c1 
FROM t1;
SELECT /*+PARALLEL(t1,3) PARALLEL(t2,4)*/ t1.c1,t2.c2
FROM t1,t2;

SELECT /*+PARALLEL(t2,4)*/ c1 --无效
FROM t1
UNION
SELECT c2 
FROM t2;

# leading提示项

使用LEADING提示项时,需同时使用hint_intersperse指定表名 [表名],表示在多表关联的查询中,提示优化器按照指定顺序访问表。

LEADING提示的表访问顺序为建议顺序,非强制,当遇到无法调整的JOIN类型(OUTER)时,则不会改变顺序。

当hint_intersperse中指定表数量超过参与JOIN的实际表数量时,优化器不接受此项提示。

当hint_intersperse中出现无效的表时,优化器不接受在该表之后指定的顺序,但仍接受该表之前所指定的顺序。

示例

SELECT /*+ LEADING(b e)*/ *
FROM branches b, department d, employees e
WHERE b.branch_no = e.branch
AND d.deparment_no = e.department
AND b.branch_no = '0401'
AND d.deparment_no = '000';

# no_use_hash提示项

使用NO_USE_HASH提示项时,需同时使用hint_intersperse指定表名 [表名],表示在指定的这些表与其他表进行关联查询时,提示优化器不进行HASH JOIN 。

示例

SELECT /*+ NO_USE_HASH(d) */ *
FROM branches b, department d, employees e
WHERE b.branch_no = e.branch
AND d.deparment_no = e.department;

# no_use_merge提示项

使用NO_USE_MERGE提示项时,需同时使用hint_intersperse指定表名 [表名],表示在指定的这些表与其他表进行关联查询时,提示优化器不进行MERGE JOIN。

示例

SELECT /*+ NO_USE_MERGE(d e) */ *
FROM branches b, department d, employees e
WHERE b.branch_no = e.branch
AND d.deparment_no = e.department;

# no_use_nl提示项

使用NO_USE_NL提示项时,需同时使用hint_intersperse指定表名 [表名],表示在指定的这些表与其他表进行关联查询时,提示优化器不进行NEST LOOPS。

示例

SELECT /*+ NO_USE_NL(a) */ *
FROM area a, branches b
WHERE a.area_no = b.area_no
AND a.area_no='04'
AND b.branch_no='0401';

# use_hash提示项

使用USE_HASH提示项时,需同时使用hint_intersperse指定表名 [表名],表示在指定的这些表与其他表进行关联查询时,提示优化器进行HASH JOIN 。

示例

SELECT /*+ USE_HASH(d) */ *
FROM branches b, department d, employees e
WHERE b.branch_no = e.branch
AND d.deparment_no = e.department;

# use_merge提示项

使用USE_MERGE提示项时,需同时使用hint_intersperse指定表名 [表名],表示在指定的这些表与其他表进行关联查询时,提示优化器进行MERGE JOIN。

示例

SELECT /*+ USE_MERGE(d e) */ *
FROM branches b, department d, employees e
WHERE b.branch_no = e.branch
AND d.deparment_no = e.department;

# use_nl提示项

使用USE_NL提示项时,需同时使用hint_intersperse指定表名 [表名],表示在指定的这些表与其他表进行关联查询时,提示优化器进行NEST LOOPS。

示例

SELECT /*+ USE_NL(a) */ *
FROM area a, branches b
WHERE a.area_no = b.area_no
AND a.area_no='04'
AND b.branch_no='0401';

# selectivity提示项

使用SELECTIVITY提示项时,语法与其他hint不同,SELECTIVITY提示项只可跟在filter后,表示指定这些filter的选择率,其后需跟一个0到1之间的浮点数。

AND、OR和BETWEEN的选择率无法通过hint的方式指定,只能通过计算得到。

示例

SELECT c11
FROM hint_tb1
WHERE c11 > 15 SELECTIVITY 0.8;

# bulkload提示项

使用BULKLOAD提示项时,无需指定表名,仅LSC表能够使用该提示项,表示对LSC表进行批量插入,可以减少redo的产生,提高插入速度,但是对事务可见性有一定的影响,详见下文约束项。

示例(LSC表)

INSERT /*+ BULKLOAD */ INTO area  VALUES ('06','华东','Shanghai');

使用BULKLOAD提示项对表进行插入优化时,在事务提交前,保证其他事务对该插入的数据不可见,不保证本插入事务对新插入数据的可见性。事务提交后,保证该插入的数据对本会话和其他事务可见。

BULKLOAD提示项的使用存在如下约束:

  • 不支持多表插入使用BULKLOAD提示项。
  • 开启自动提交后,不允许使用BULKLOAD提示项进行插入优化。
  • 一个事务内使用BULKLOAD提示项进行插入后,不允许创建savepoint。已有savepoint的情况下,也不允许使用BULKLOAD提示项进行插入。
  • 一个事务内只允许对一张表使用BULKLOAD提示项进行插入,否则报错。
  • 由使用了BULKLOAD提示项的插入语句导致的部分失败会导致整个事务回滚,语法错误、校验错误、部分资源分配的失败则只会使得当前语句回滚。若失败后整个事务被回滚,报错信息会进行提示。
  • 一个事务内使用BULKLOAD提示项进行插入以后,不允许再对该表进行除插入、查询外的其他DML操作,也不允许对该表进行insert on duplicate update操作。
  • 不支持insert带子查询的语句使用BULKLOAD提示项,也不支持insert on duplicate update语句使用BULKLOAD提示项。

示例(LSC表)

--开启自动提交后,使用BULKLOAD提示项进行插入,提示错误
SET AUTOCOMMIT ON;
INSERT /*+ BULKLOAD */ INTO area  VALUES ('06','华东','Shanghai');
YAS-03728 cannot execute bulkload if autocommit is on

--事务内,area表使用BULKLOAD提示项进行插入后,再对department表使用BULKLOAD提示项进行插入,提示报错
INSERT /*+ BULKLOAD */ INTO area  VALUES ('06','华东','Shanghai');
INSERT /*+ BULKLOAD */ INTO department VALUES ('002','财务部');
YAS-03728 cannot execute bulkload if bulkloading table has changed within the transaction

--由上条语句对department表使用BULKLOAD提示项进行插入提示报错后,只会回滚当前语句,执行commit再查area表可以查到('06','华东','Shanghai')这条插入
COMMIT;
SELECT * FROM area;
AREA_NO AREA_NAME        DHQ          
------- ---------------- -------------
01      华东           Shanghai       
02      华西           Chengdu        
03      华南           Guangzhou      
04      华北           Beijing        
05      华中           Wuhan  
06      华东           Shanghai

建议在导入期间不需要查询,不需要进行其他的DML操作的情况下,使用BULKLOAD提示项进行插入,参考场景:增量批量导入。

# deduplicate提示项

使用DEDUPLICATE提示项时,无需指定表名,仅LSC表能够使用该提示项,且只能与BULKLOAD提示项同时使用,表示对有唯一约束、主键约束的LSC表进行批量插入的时候,若插入数据与已有数据产生冲突,则进行去重操作,将冲突的数据对应的行替换成新插入的行。

示例(LSC表)

--未使用DEDUPLICATE提示项时,插入重复数据报错违反唯一约束
INSERT /*+ BULKLOAD */ INTO area  VALUES ('05','华南','Shenzhen');
YAS-02030 unique constraint violated
--使用DEDUPLICATE提示项后,插入重复数据成功,查询表的AREA_NO='05'的数据已经被替换成新插入的数据
INSERT /*+ BULKLOAD DEDUPLICATE */ INTO area  VALUES ('05','华南','Shenzhen');
SELECT * FROM area WHERE AREA_NO='05';
AREA_NO AREA_NAME        DHQ    
------- ---------------- -------------
05      华南           Shenzhen