写点什么

金仓数据库 KingbaseES 如何通过 Hint 影响执行计划

作者:金仓技术
  • 2025-04-23
    四川
  • 本文字数:5027 字

    阅读完需:约 16 分钟

金仓数据库KingbaseES如何通过Hint影响执行计划


什么是 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 注释可以分为六类:

  1. 单表扫描方式的 Hint

  2. 单表并行方式的 Hint

  3. 多表连接方式的 Hint

  4. 多表连接顺序的 Hint

  5. 为单表扫描或多表连接指定返回记录行数的 Hint

  6. 修改配置参数的 Hint


单表扫描方式的 Hint

KES 中对一个数据表的扫描方式有多种,如全表顺序扫描,索引扫描,位图扫描,TID 扫描等。优化器会根据表中的数据量,过滤条件的选择率以及每种扫描方式的代价因子等参数计算每种扫描方式的代价,然后选择一个代价最小的扫描方式。但由于一些原因,如代价的估算是基于数据采样,或优化器不计算数据的某些属性(比如列之间的相关性)等,优化器有时选择的计划并不一定是最优的。此时用户可以通过 hint 来指定扫描的方式。KES 的单表扫描 Hint 有 15 个,分别为以下 5 种基本类型,还有 5 种反逻辑 Hint 和 5 种强制类型 Hint。如 SeqScan Hint 指定了要对表进行全表扫描,反逻辑的 Hint NoSeqScan 指定不要对表进行全表扫描,而强制逻辑的 Hint ForceSeqScan 指定强制对表进行全表扫描。

  1. SeqScan(table):在表上使用顺序扫描。

  2. TidScan(table):在表上使用 Tid 扫描。

  3. IndexScan(table[ index...]):在表上使用索引扫描,只限制指定的索引。

  4. IndexOnlyScan(table[ index...]):在表上使用 Indexonly scan,限制特定的索引。当 Indexonlyscan 不可用时,可以使用索引扫描。

  5. 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 主要有以下两种形式:

  1. Leading(table table[ table...]):强制使用指定的连接顺序,但不指定内外表。如 Leading(a b c), 指定表 a,b 首先做连接操作,然后和表 c 做连接。但并未指定 a,b 连接时哪个为外表,哪个为内表。

  2. 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 调优的利器。

用户头像

金仓技术

关注

还未添加个人签名 2025-03-24 加入

还未添加个人简介

评论

发布
暂无评论
金仓数据库KingbaseES如何通过Hint影响执行计划_KingbaseES_金仓技术_InfoQ写作社区