You need to enable JavaScript to run this app.
导航
通过注释提示改变既定执行计划(pg_hint_plan)
最近更新时间:2025.02.21 00:18:19首次发布时间:2025.02.21 00:18:19

pg_hint_plan 通过特殊形式的注释中的提示短语来控制执行计划,使 PostgreSQL 改变其既定的执行计划。

背景信息

pg_hint_plan 同调整 GUC 变量来影响执行计划不同,GUC 变量的调整会影响当前会话所有 SQL, 而 pg_hint_plan 只影响指定的单个 SQL 执行计划,达到优化特定 SQL 执行计划的目的。

前提条件

使用该插件前,需要将 pg_stat_statements 与 pg_hint_plan 加入到 shared_preload_libraries 参数中。关于如何修改参数,请参见修改参数

创建与删除插件

  • 创建插件

    CREATE EXTENSION pg_hint_plan;
    
  • 删除插件

    DROP EXTENSION pg_hint_plan;
    

使用

使用提示短语

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

postgres=# create table  a(id int, name text);
CREATE TABLE
postgres=# create table  b(id int, name text);
CREATE TABLE
postgres=# explain select * from a join b on a.id=b.id;
                            QUERY PLAN                            
------------------------------------------------------------------
 Merge Join  (cost=176.34..303.67 rows=8064 width=72)
   Merge Cond: (a.id = b.id)
   ->  Sort  (cost=88.17..91.35 rows=1270 width=36)
         Sort Key: a.id
         ->  Seq Scan on a  (cost=0.00..22.70 rows=1270 width=36)
   ->  Sort  (cost=88.17..91.35 rows=1270 width=36)
         Sort Key: b.id
         ->  Seq Scan on b  (cost=0.00..22.70 rows=1270 width=36)
(8 rows)

通过 /*+ 开始,*/ 结尾指定 HashJoin 被选择为连接方法 plan 为:

postgres=# /*+
postgres*#    HashJoin(a b)
postgres*#  */
postgres-# explain select * from a join b on a.id=b.id;
                            QUERY PLAN                            
------------------------------------------------------------------
 Hash Join  (cost=38.58..346.71 rows=8064 width=72)
   Hash Cond: (a.id = b.id)
   ->  Seq Scan on a  (cost=0.00..22.70 rows=1270 width=36)
   ->  Hash  (cost=22.70..22.70 rows=1270 width=36)
         ->  Seq Scan on b  (cost=0.00..22.70 rows=1270 width=36)
(5 rows)

使用提示表

当查询语句无法编辑时,使用提示短语会遇到很多不便。此时,可以将提示放在特殊的表 hint_plan.hints 中,表结构如下所示:

列名描述
id提示行唯一标识符。该列按顺序自动填充。
norm_query_string与要提示的查询相匹配的模式。查询中的常量必须替换为 ?。空格也需要特别注意。
application_name应用该提示的会话名称。空字符串表示任何 application_name 的会话。
hints提示短语。不包括注释标记。

示例

--hint_plan.hints 表中定义了执行 EXPLAIN (COSTS false) SELECT * FROM a WHERE a.id = xxx 时使用 SeqScan(a) 的提示
postgres=# INSERT INTO hint_plan.hints(norm_query_string, application_name, hints) VALUES('EXPLAIN (COSTS false) SELECT * FROM a WHERE a.id = ?;', '', 'SeqScan(a)');
INSERT 0 1
postgres=# update hint_plan.hints set hints='IndexScan(a)' where id=1;
UPDATE 1

提示类型

根据提示短语影响执行计划的方式,可以分为如下六类:扫描方法、连接方法、连接顺序、行号校正、并行查询和GUC设置。可以在提示列表中查看每种类型的提示短语列表。

扫描方法提示

  • 扫描方法提示对目标表强制执行特定的扫描方法。pg_hint_plan 通过表的别名(如果存在的话)来识别目标表。扫描方法是SeqScanIndexScan等。

  • 扫描提示对普通表、继承表、UNLOGGED 表、临时表和系统表有效,对外部表、表函数、VALUES 子句、通用表达式、视图和子查询无效。

  • 示例命令如下:

    postgres=# /*+
    postgres*#    IndexScan(a a_pk)
    postgres*#    SeqScan(b)
    postgres*#  */
    postgres-# explain select * from a join b on a.id=b.id;
                                     QUERY PLAN                                  
    -----------------------------------------------------------------------------
     Hash Join  (cost=14.89..48.70 rows=635 width=45)
       Hash Cond: (b.id = a.id)
       ->  Seq Scan on b  (cost=0.00..22.70 rows=1270 width=36)
       ->  Hash  (cost=13.64..13.64 rows=100 width=9)
             ->  Index Scan using a_pk on a  (cost=0.14..13.64 rows=100 width=9)
    (5 rows)
    

连接方法提示

  • 连接方法提示强制指定相关表格聚合在一起的方法。

  • 连接方法提示对普通表 、继承表、UNLOGGED 表、临时表、外部表、系统表、表函数、VALUES 子句和通用表达式有效,对视图和子查询无效。

    postgres=# /*+
    postgres*#    MergeJoin(a b)
    postgres*#    IndexScan(a a_pk)
    postgres*#    SeqScan(b)
    postgres*#  */
    postgres-# explain select * from a join b on a.id=b.id;
                                  QUERY PLAN                               
    -----------------------------------------------------------------------
     Merge Join  (cost=88.31..114.76 rows=635 width=45)
       Merge Cond: (a.id = b.id)
       ->  Index Scan using a_pk on a  (cost=0.14..13.64 rows=100 width=9)
       ->  Sort  (cost=88.17..91.35 rows=1270 width=36)
             Sort Key: b.id
             ->  Seq Scan on b  (cost=0.00..22.70 rows=1270 width=36)
    (6 rows)
    

连接顺序提示

  • 此提示“ Leading”在两个或多个表的连接顺序。这里有两种强制指定方法:

    • 强制执行特定的连接顺序,但不限制每个连接级别的方向。

    • 强制连接方向。

  • 示例命令如下:

    postgres=# /*+
    postgres*#     NestLoop(a b)
    postgres*#     MergeJoin(a b c)
    postgres*#     Leading(a b c)
    postgres*#  */
    postgres-# EXPLAIN SELECT * FROM a
    postgres-#      JOIN b ON (a.id = b.id)
    postgres-#      JOIN c ON (b.id = c.id);
                                       QUERY PLAN                                   
    --------------------------------------------------------------------------------
     Merge Join  (cost=368.33..431.99 rows=4032 width=81)
       Merge Cond: (a.id = c.id)
       ->  Sort  (cost=280.16..281.75 rows=635 width=45)
             Sort Key: a.id
             ->  Nested Loop  (cost=0.14..250.60 rows=635 width=45)
                   ->  Seq Scan on b  (cost=0.00..22.70 rows=1270 width=36)
                   ->  Index Scan using a_pk on a  (cost=0.14..0.17 rows=1 width=9)
                         Index Cond: (id = b.id)
       ->  Sort  (cost=88.17..91.35 rows=1270 width=36)
             Sort Key: c.id
             ->  Seq Scan on c  (cost=0.00..22.70 rows=1270 width=36)
    (11 rows)
    

行号纠正提示

  • 行号纠正提示会纠正由于计划器限制而导致的行号错误。

  • 示例命令如下:

    /*+ Rows(a b #10) */ SELECT... ;     //设置连接结果的行数量为 10。
    /*+ Rows(a b +10) */ SELECT... ;     //行数量增加 10。
    /*+ Rows(a b -10) */ SELECT... ;     //行数量减去 10。
    /*+ Rows(a b *10) */ SELECT... ;     //将行数量扩大至原来的 10 倍。
    

并行执行提示

  • 并行查询提示在扫描时会强制并行执行配置。

  • 第三个参数表示强度。soft 表示 pg_hint_plan 只更改 max_parallel_worker_per_gather;hard 更改所有参数,使提示数字有效。

  • 并行查询提示对普通表、继承表、UNLOGGED 表和系统表有效;对外部表、表函数、VALUSES 子句、通用表表达式(CTEs)、视图和子查询无效。可以通过指定内部表的真实名或别名来作为目标对象。

  • 下面两个示例说明在每个表上执行查询的方式不同:

    postgres=# explain /*+ Parallel(a 2 hard) Parallel(b 3 hard) */
    postgres-# select * from a join b on a.id=b.id;
                                     QUERY PLAN                                  
    -----------------------------------------------------------------------------
     Hash Join  (cost=1.25..12.36 rows=635 width=45)
       Hash Cond: (b.id = a.id)
       ->  Gather  (cost=0.00..0.00 rows=1270 width=36)
             Workers Planned: 3
             ->  Parallel Seq Scan on b  (cost=0.00..0.00 rows=410 width=36)
       ->  Hash  (cost=0.00..0.00 rows=100 width=9)
             ->  Gather  (cost=0.00..0.00 rows=100 width=9)
                   Workers Planned: 2
                   ->  Parallel Seq Scan on a  (cost=0.00..0.00 rows=59 width=9)
    (9 rows)
    
    postgres=# EXPLAIN /*+ Parallel(a 5 hard) */ SELECT sum(id) FROM a;
                                        QUERY PLAN                                    
    ----------------------------------------------------------------------------------
     Finalize Aggregate  (cost=3703.27..3703.28 rows=1 width=8)
       ->  Gather  (cost=3702.75..3703.26 rows=5 width=8)
             Workers Planned: 5
             ->  Partial Aggregate  (cost=2702.75..2702.76 rows=1 width=8)
                   ->  Parallel Seq Scan on a  (cost=0.00..0.00 rows=1081100 width=4)
    (5 rows)
    

设置临时 GUC 参数

  • 在计划的时候临时改变 GUC 参数。如果计划器的配置参数没有与其他任何提示冲突,所设置的 GUC 就有效。同样的 GUC 参数设置以最后一次为准。

  • 示例命令如下:

    postgres=# /*+ Set(random_page_cost 1.2) */
    SELECT * FROM a ta WHERE name = '10abc';
     id | name  
    ----+-------
     10 | 10abc
    (1 row)
    

提示列表

所有提示支持的格式如下表:

类别格式说明
扫描方法提示SeqScan(table)强制序列扫描。
TidScan(table)强制 TID 扫描。
IndexScan(table[ index...])强制索引扫描,可以指定某个索引。
IndexOnlyScan(table[ index...])强制仅使用索引扫描,可以指定某个索引。
BitmapScan(table[ index...])强制使用 Bitmap 扫描。
NoSeqScan(table)强制不使用序列扫描。
NoTidScan(table)强制不使用 TID 扫描。
NoIndexScan(table)强制不使用索引扫描。
NoIndexOnlyScan(table)强制不使用索引扫描,仅扫描表。
NoBitmapScan(table)强制不使用 Bitmap 扫描。
连接方法提示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()强制连接的顺序和方向。
行号纠正提示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 参数设置为该值。

其他

更多信息,请参见 PostgreSQL 官方文档