ud_hint_plan

概述

UnvDB使用基于代价的优化器,该优化器的代价计算利用的是数据统计信息,而不是静态规则。对于一条SQL语句,优化器会估计所有可能的执行计划的代价,然后最终选择代价最低的执行计划。由于优化器不会考虑列之间的相关性,因此,最终选出来的执行计划可能并不是完美的。

ud_hint_plan允许我们在sql语句中通过特殊格式的提示来调整执行计划,达到优化执行计划的目的。当判断数据库优化器生成的执行计划不是最优的时候,可以在不修改SQL的情况下(例如:等价改写、非等价改写)通过添加Hint,来影响执行计划的生成。

验证安装

testdb=# create extension ud_hint_plan;
CREATE EXTENSION

使用方法

基本使用

ud_hint_plan在目标SQL语句中读取特殊格式的提示短语。提示短语以字符~/+~开始,~/~结尾。提示短语由提示名和参数(用括号包起来,两个参数之间用空格分隔)组成。为了增加可读性,每一个提示短语可另起一行。例如:

testdb=# explain (analyze,verbose,buffers) select /*+NestLoop(t2 t1)*/* from tenk1 t1, tenk2 t2 where t1.id = t2.id and t1.name = '一';

在上面的示例中,NestLoop为新的连接方法

提示表

当查询语句无法编辑时,是很不方便的。面对这种情况,可以将提示放在特殊的表hint_plan.hints中,表结构如下所示:

testdb=# \d hint_plan.hints
                                     Table "hint_plan.hints"
      Column       |  Type   | Collation | Nullable |                   Default 
                  
-------------------+---------+-----------+----------+---------------------------
------------------
 id                | integer |           | not null | nextval('hint_plan.hints_i
d_seq'::regclass)
 norm_query_string | text    |           | not null | 
 application_name  | text    |           | not null | 
 hints             | text    |           | not null | 
列名 描述
id 提示行唯一标识符,该列按顺序自动填充,不需要我们维护
norm_query_string 与要提示的查询相匹配的模式,查询中的常量必须替换为?,注意别忘记空格
application_name 应用的名字,为空字符串的话,表示任何的会话都可以
hints 需要添加的提示短语,其中不包括注释标记
## 提示类型
根据对象类型以及如何影响计划提示短语可分为六类。扫描方法、连接方法、连接顺序、行号校正、并行查询和GUC设置。您可以在提示列表中看到每种类型的提示短语列表。
分类 格式 说明
扫描方法 SeqScan(table) 强制对指定表使用序列扫描。
TidScan(table) 强制对指定表使用TID扫描。
IndexScan(table[ index...]) 强制对指定表使用索引扫描,可以指定某个索引。
IndexOnlyScan(table[ index...]) 强制对指定表仅使用索引扫描,可以指定某个索引。
BitmapScan(table[ index...]) 强制对指定表使用位图扫描,可以指定某个索引。
IndexScanRegexp(table[ POSIX Regexp...]) 强制对指定表使用索引扫描或仅索引扫描或位图扫描。使用正则匹配。
IndexOnlyScanRegexp(table[ POSIX Regexp...])
BitmapScanRegexp(table[ POSIX Regexp...])
NoSeqScan(table) 对指定表禁止使用序列扫描。
NoTidScan(table) 对指定表禁止使用TID扫描。
NoIndexScan(table) 对指定表禁止使用索引扫描(包括仅索引扫描)。
NoIndexOnlyScan(table) 对指定表禁止使用仅索引扫描。
NoBitmapScan(table) 对指定表禁止使用位图扫描。
连接方法 NestLoop(table table[ table...]) 强制对指定表使用嵌套循环连接。
HashJoin(table table[ table...]) 强制对指定表使用哈希连接。
MergeJoin(table table[ table...]) 强制对指定表使用合并连接。
NoNestLoop(table table[ table...]) 对指定表禁止使用嵌套循环连接。
NoHashJoin(table table[ table...]) 对指定表禁止使用哈希连接。
NoMergeJoin(table table[ table...]) 对指定表禁止使用合并连接
连接顺序 Leading(table table[ table...]) 强制连接顺序。
Leading(<join pair>) 强制连接顺序和方向,
行号校正 Rows(table table[ table...] correction) 纠正由指定表组成的联接结果的行号。可用的校正方法有绝对值(#<n>),加法(+ <n>),减法(-<n>)和乘法(* <n>)。<n>是函数strtod()可以读取的字符串。
并行查询配置 Parallel(table <# of workers> [soft|hard]) 强制或禁止指定表并行查询。<worker#>是所需的并行工作进程数量,0表示禁止并行查询。第三个参数如果是soft(默认),表示仅更改max_parallel_workers_per_gather,且其他内容由计划器自主选择; 如果是hard,表示所有相关参数都会被强制指定。
GUC Set(GUC-param value) 计划器运行时,设置GUC参数。

用法示例

执行原查找计划:

testdb=# set enable_hashjoin = on;
SET
testdb=# explain (analyze,verbose,buffers) select * from tenk1 t1, tenk2 t2 where t1.id = t2.id and t1.name = '一';
                                                         QUERY PLAN             
                                            
--------------------------------------------------------------------------------
--------------------------------------------
Hash Join  (cost=374.29..774.70 rows=1 width=32) (actual time=5.083..1379.096 r
ows=1 loops=1)
   Output: t1.id, t1.num, t1.name, t1.sex, t2.id, t2.num, t2.name, t2.sex
   Hash Cond: (t2.id = t1.id)
   Buffers: shared hit=226
   ->  Seq Scan on public.tenk2 t2  (cost=0.00..322.02 rows=20902 width=16) (act
ual time=0.093..678.793 rows=20902 loops=1)
         Output: t2.id, t2.num, t2.name, t2.sex
         Buffers: shared hit=113
   ->  Hash  (cost=374.28..374.28 rows=1 width=16) (actual time=4.340..4.443 row
s=1 loops=1)
         Output: t1.id, t1.num, t1.name, t1.sex
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         Buffers: shared hit=113
         ->  Seq Scan on public.tenk1 t1  (cost=0.00..374.28 rows=1 width=16) (a
ctual time=0.097..4.204 rows=1 loops=1)
               Output: t1.id, t1.num, t1.name, t1.sex
               Filter: ((t1.name)::text = '一'::text)
               Rows Removed by Filter: 20901
               Buffers: shared hit=113
 Query Identifier: -5240454774119237226
 Planning:
   Buffers: shared hit=19
 Planning Time: 0.775 ms
 Execution Time: 1379.314 ms
(21 rows)

Hint提示后的执行计划:

testdb=# explain (analyze,verbose,buffers) select /*+NestLoop(t2 t1)*/* from tenk1 t1, tenk2 t2 where t1.id = t2.id and t1.name = '一';
                                                         QUERY PLAN             
                                            
--------------------------------------------------------------------------------
--------------------------------------------
Nested Loop  (cost=0.00..957.57 rows=1 width=32) (actual time=0.307..1224.259 r
ows=1 loops=1)
   Output: t1.id, t1.num, t1.name, t1.sex, t2.id, t2.num, t2.name, t2.sex
   Join Filter: (t1.id = t2.id)
   Rows Removed by Join Filter: 20901
   Buffers: shared hit=226
   ->  Seq Scan on public.tenk1 t1  (cost=0.00..374.28 rows=1 width=16) (actual 
time=0.097..4.643 rows=1 loops=1)
         Output: t1.id, t1.num, t1.name, t1.sex
         Filter: ((t1.name)::text = '一'::text)
         Rows Removed by Filter: 20901
         Buffers: shared hit=113
   ->  Seq Scan on public.tenk2 t2  (cost=0.00..322.02 rows=20902 width=16) (act
ual time=0.089..599.928 rows=20902 loops=1)
         Output: t2.id, t2.num, t2.name, t2.sex
         Buffers: shared hit=113
 Query Identifier: -5240454774119237226
 Planning Time: 0.336 ms
 Execution Time: 1224.485 ms
(16 rows)

固定执行计划

由于生产环境中有些应用是不可以修改代码的,加Hint也不可以,ud_hint_plan支持固定执行计划,hint_plan.hints表前文已经介绍过,下面介绍使用方法:

插入数据

testdb=# insert into hint_plan.hints(norm_query_string,application_name,hints)
testdb=# values ('explain (analyze,verbose,buffers) select from tenk1 t1, tenk2 t2 where t1.id = t2.id and t1.name = ?;','','NestLoop(t2 t1)');
INSERT 0 1
testdb=# select * from hint_plan.hints;
 id |                                           norm_query_string               
                            | application_name |      hints      
----+---------------------------------------------------------------------------
----------------------------+------------------+-----------------
  1 | explain (analyze,verbose,buffers) select from tenk1 t1, tenk2 t2 where t1.
id = t2.id and t1.name = ?; |                  | NestLoop(t2 t1)
(1 row)

开启参数ud_hint_plan.enable_hint_table

testdb=# show ud_hint_plan.enable_hint_table;
ud_hint_plan.enable_hint_table 
--------------------------------
 off
(1 row)

set ud_hint_plan.enable_hint_table=on;  --当前会话生效:
SET

testdb=# set ud_hint_plan.enable_hint_table=on; --永久生效,修改完此参数记得重启数据库
SET
testdb=# alter system  set ud_hint_plan.enable_hint_table=on;
ALTER SYSTEM

ud_hint_plan参数介绍

| 参数名 | 描述 |
|———-|———-| |ud_hint_plan.enable_hint |True为启动,默认为on|
|ud_hint_plan.enable_hint_table|True为可以在表上加Hint,默认为off|
|ud_hint_plan.parse_messages|指定提示解析错误的日志级别。有效值为.error ,warning ,notice ,info ,log ,debug|
|ud_hint_plan.debug_print|控制调试打印和详细信息。有效的值是off ,on ,detailed ,verbose,默认为off|
|ud_hint_plan.message_level|指定调试打印的消息级别。有效值为error ,warning ,notice ,info ,log ,debug|

查看计划是否固定

testdb=# explain (analyze,verbose,buffers) select from tenk1 t1, tenk2 t2 where t1.id = t2.id and t1.name = '一';
                                                        QUERY PLAN              
                                           
--------------------------------------------------------------------------------
-------------------------------------------
Nested Loop  (cost=0.00..957.57 rows=1 width=0) (actual time=0.303..1310.722 ro
ws=1 loops=1)
   Join Filter: (t1.id = t2.id)
   Rows Removed by Join Filter: 20901
   Buffers: shared hit=226
   ->  Seq Scan on public.tenk1 t1  (cost=0.00..374.28 rows=1 width=4) (actual t
ime=0.095..4.966 rows=1 loops=1)
         Output: t1.id, t1.num, t1.name, t1.sex
         Filter: ((t1.name)::text = '一'::text)
         Rows Removed by Filter: 20901
         Buffers: shared hit=113
   ->  Seq Scan on public.tenk2 t2  (cost=0.00..322.02 rows=20902 width=4) (actu
al time=0.090..644.165 rows=20902 loops=1)
         Output: t2.id, t2.num, t2.name, t2.sex
         Buffers: shared hit=113
 Query Identifier: -7891239385585250035
 Planning Time: 0.346 ms
 Execution Time: 1310.943 ms
(15 rows)