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)