什么是Hint?
顾名思义,Hint即为“提示”,是对于KES优化器的提示,具体来说,它的作用是通过在SQL语句的注释中使用特殊形式的hint短语来影响SQL语句所用的执行计划,达到通过人工干预执行计划生成的目的。
如下的用例说明了如何通过hint人工干预执行计划:对表t1的位图扫描预估代价为22.72,全表顺序扫描的方式预估代价为25.88,所以优化器会选择位图扫描作为执行计划。而通过在SQL中加入Hint 注释的方式就可以强制优化器选择全表顺序扫描。
TEST=# explain select * from t1 where id<10;
QUERY PLAN
------------------------------------------------------------------------
Bitmap Heap Scan on t1 (cost=7.43..22.72 rows=423 width=36)
Recheck Cond: (id < 10)
-> Bitmap Index Scan on t1_pkey (cost=0.00..7.32 rows=423 width=0)
Index Cond: (id < 10)
(4 rows)
TEST=# explain select/*+seqscan(t1)*/ * from t1 where id<10;
QUERY PLAN
------------------------------------------------------
Seq Scan on t1 (cost=0.00..25.88 rows=423 width=36)
Filter: (id < 10)
(2 rows)
为什么要使用Hint
对于一条给定的SQL语句,Kingbase ES通常会有多个执行计划可供选择。每个执行计划执行效率不同,但都可以返回正确的结果。优化器会为每个执行计划计算一个预估代价,然后选择预期运行最快的执行计划。随着优化器技术的不断演进,在通常情况下,优化器的算法足以选择一条高效的执行计划。但是由于客户现场SQL查询使用场景的多样性,有时也需要对执行计划的选择进行人工干预。对于这种情况,数据库业界的通常做法为通过Hint功能来为用户提供一个可以人工选择执行计划的方式。
Hint的格式与原理
本文将SQL中每个以Select开始的语句块称为一个查询语句块,Kingbase ES Hint的作用范围即为查询语句块。一个SQL可以包含多个语句块,例如select/*+Hint1*/ * from (select /*+Hint2*/ * from t1)x。Hint注释以相连的三个字符“/*+”开始,以相连的字符“*/”结束,可以在注释中同时指定多个hint,之间使用空格分隔。Hint的格式为 Hint名称(参数列表),多个参数之间以空格分割,如/*+ SeqScan(t1) SeqScan(t2) */。Hint关键字不区分大小写。Hint的格式如果出现错误,会被作为一个普通的注释,即该Hint不起作用,也不会影响SQL语句的执行。
KES的Hint实现仍然是基于KES优化器的选择最小代价原理。KES Hint的实现分为两种方式,一种为建议类型的Hint,该hint会把其他竞争者(执行计划)的路径代价都加上1.0e10从而使自己的执行代价最小。另一种为强制类型的Hint,该hint在存在hint指定执行计划的前提下,会把其他竞争者都删除,这样优化器只有唯一一个计划可供选择
如何使用 KES Hint
KES的hint使用非常简单,只需设置enable_hint参数为on,然后在SQL语句的Select之后加上Hint注释即可。根据HINT注释作用的对象种类以及如何来影响计划,Hint注释可以分为六类:
- 单表扫描方式的Hint
- 单表并行方式的Hint
- 多表连接方式的Hint
- 多表连接顺序的Hint
- 为单表扫描或多表连接指定返回记录行数的Hint
- 修改配置参数的Hint
单表扫描方式的Hint
KES中对一个数据表的扫描方式有多种,如全表顺序扫描,索引扫描,位图扫描,TID扫描等。优化器会根据表中的数据量,过滤条件的选择率以及每种扫描方式的代价因子等参数计算每种扫描方式的代价,然后选择一个代价最小的扫描方式。但由于一些原因,如代价的估算是基于数据采样,或优化器不计算数据的某些属性(比如列之间的相关性)等,优化器有时选择的计划并不一定是最优的。此时用户可以通过hint来指定扫描的方式。KES的单表扫描Hint有15个,分别为以下5种基本类型,还有5种反逻辑Hint和5种强制类型Hint。如SeqScan Hint指定了要对表进行全表扫描,反逻辑的Hint NoSeqScan指定不要对表进行全表扫描,而强制逻辑的Hint ForceSeqScan指定强制对表进行全表扫描。
- SeqScan(table):在表上使用顺序扫描。
- TidScan(table):在表上使用Tid扫描。
- IndexScan(table[ index...]):在表上使用索引扫描,只限制指定的索引。
- IndexOnlyScan(table[ index...]):在表上使用Indexonly scan,限制特定的索引。当Indexonlyscan不可用时,可以使用索引扫描。
- BitmapScan(table[ index...]):强制在表上使用位图扫描。
单表并行方式的Hint
该Hint指定的表上强制执行或者禁止并行执行,格式为Parallel(table <# of workers> )。Table为表名,<# of workers>是所需的并行workers数, 0代表禁止并行执行。如下图所示,加入Hint后对表a 的扫描方式变为并行。
TEST=# explain select count(1) from a;
QUERY PLAN
-----------------------------------------------------------
Aggregate (cost=41.88..41.88 rows=1 width=8)
-> Seq Scan on a (cost=0.00..35.50 rows=2550 width=0)
(2 rows)
TEST=# explain select/*+parallel(a 2)*/ count(1) from a;
QUERY PLAN
-------------------------------------------------------------------------
Aggregate (cost=6.38..6.38 rows=1 width=8)
-> Gather (cost=0.00..0.00 rows=2550 width=0)
Workers Planned: 2
-> Parallel Seq Scan on a (cost=0.00..0.00 rows=1062 width=0)
(4 rows)
多表连接方式的Hint
KES对多个表进行连接操作时有三种连接方式,分别为循环嵌套连接,散列连接和排序合并连接。对应的三种hint为NestLoop,HashJoin,MergeJoin以及其对应的反逻辑NoNestLoop,NoHashJoin,NoMergeJoin和强制逻辑ForceNestLoop,ForceHashJoin,ForceMergeJoin。以下图为例,表a,b,c以散列连接的方式连接,通过Hint可以指定使用循环嵌套连接。
TEST=# explain select * from a,b,c where a.id=b.id and a.id=c.id;
QUERY PLAN
----------------------------------------------------------------------------------
Hash Join (cost=9474.31..9520.64 rows=64069 width=45)
Hash Cond: (a.id = b.id)
-> Seq Scan on a (cost=0.00..35.50 rows=2550 width=4)
-> Hash (cost=9411.50..9411.50 rows=5025 width=41)
-> Hash Join (cost=7730.00..9411.50 rows=5025 width=41)
Hash Cond: (c.id = b.id)
-> Seq Scan on c (cost=0.00..35.50 rows=2550 width=4)
-> Hash (cost=3667.00..3667.00 rows=200000 width=37)
-> Seq Scan on b (cost=0.00..3667.00 rows=200000 width=37)
(9 rows)
TEST=# explain select/*+nestloop(a b c)*/ * from a,b,c where a.id=b.id and a.id=c.id;
QUERY PLAN
----------------------------------------------------------------------------
Nested Loop (cost=7730.00..201659.62 rows=64069 width=45)
Join Filter: (a.id = c.id)
-> Hash Join (cost=7730.00..9411.50 rows=5025 width=41)
Hash Cond: (a.id = b.id)
-> Seq Scan on a (cost=0.00..35.50 rows=2550 width=4)
-> Hash (cost=3667.00..3667.00 rows=200000 width=37)
-> Seq Scan on b (cost=0.00..3667.00 rows=200000 width=37)
-> Materialize (cost=0.00..48.25 rows=2550 width=4)
-> Seq Scan on c (cost=0.00..35.50 rows=2550 width=4)
(9 rows)
多表连接顺序的Hint
除了指定多表连接的方式,也可以通过Hint指定连接的顺序。如上图中,未使用Hint时,表b和c首先进行散列连接,然后再和表a进行连接,如果使用Hint Leading(a b c), 则表a,b首先做连接操作,然后和表c做连接。
连接顺序的hint主要有以下两种形式:
- Leading(table table[ table...]):强制使用指定的连接顺序,但不指定内外表。如Leading(a b c), 指定表a,b首先做连接操作,然后和表c做连接。但并未指定a,b连接时哪个为外表,哪个为内表。
- Leading((<join pair>)):强制使用指定的连接顺序和方向。其中一个joinpair可以是一对表和/或者用括号括起来的可以构成嵌套结构的其他连接对。如以下例子,除了指定连接顺序为a,b,c之外,还指定了a b连接时a为外表, a b c连接时c为内表。
TEST=# explain select/*+leading(((a b) c))*/ * from a,b,c where a.id=b.id and a.id=c.id;
QUERY PLAN
----------------------------------------------------------------------------------
Merge Join (cost=9900.19..10886.35 rows=64069 width=45)
Merge Cond: (a.id = c.id)
-> Sort (cost=9720.41..9732.97 rows=5025 width=41)
Sort Key: a.id
-> Hash Join (cost=7730.00..9411.50 rows=5025 width=41)
Hash Cond: (a.id = b.id)
-> Seq Scan on a (cost=0.00..35.50 rows=2550 width=4)
-> Hash (cost=3667.00..3667.00 rows=200000 width=37)
-> Seq Scan on b (cost=0.00..3667.00 rows=200000 width=37)
-> Sort (cost=179.78..186.16 rows=2550 width=4)
Sort Key: c.id
-> Seq Scan on c (cost=0.00..35.50 rows=2550 width=4)
(12 rows)
单表扫描或多表连接的行数的Hint
当对基表或子查询的返回记录行数估算错误时,可能会导致优化器选取执行代价较高的计划。例如两个表执行连接时,如果优化器预估外表返回的行数较少,如返回1行,大概率会采用nestloop的连接方式。而此时如果外表实际返回行数较多且查询比较耗时,如1000行,会导致内表的耗时查询被执行1000次。此时SQL语句的执行会效率非常低。此时可以使用rows hint对外表的返回行数进行修正。Rows hint的定义为Rows(table[ table...] correction),table为表名,可用的修正方法有absolute (#<n>), addition (+<n>), subtract (-<n>) and multiplication (*<n>). <n>必须是整数。如Rows(t1 t2 #10) 指定表t1,t2的连接返回记录数为10条。
修改配置参数的Hint
Set Hint在优化器运行时修改相关的GUC参数。通过在SQL语句中加入Set Hint,可以只改变为该SQL生成执行计划时的配置参数。如下图所示,在系统中参数enable_bitmapscan=on,可以通过 Hint指定在执行select * from t1 where id<10语句时该参数被关闭。该语句执行完毕后参数值仍然是on。
TEST=# explain select * from t1 where id<10;
QUERY PLAN
------------------------------------------------------------------------
Bitmap Heap Scan on t1 (cost=7.43..22.72 rows=423 width=36)
Recheck Cond: (id < 10)
-> Bitmap Index Scan on t1_pkey (cost=0.00..7.32 rows=423 width=0)
Index Cond: (id < 10)
(4 rows)
TEST=# explain select/*+set(enable_bitmapscan off)*/ * from t1 where id<10;
QUERY PLAN
------------------------------------------------------
Seq Scan on t1 (cost=0.00..25.88 rows=423 width=36)
Filter: (id < 10)
(2 rows)
总结
本文介绍了KingbaseES Hint的使用场景、定义格式和六种Hint的使用方式。Hint是数据库很有特色的一种功能,对于一些复杂的客户场景,Hint仍然是SQL调优的利器。




