PostgreSQL SQL HINT的使用

本文涉及的产品
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
云数据库 RDS SQL Server,基础系列 2核4GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介:
PostgreSQL优化器是基于成本的 (CBO) , (当然, 如果开启了GEQO的话, 在关联表数量超过一定阈值后, 会采用GEQO, 这主要是因为在关联表太多的情况下, 穷举法可能带来巨大的PLAN开销, 所以GEQO输出的执行计划不一定是最优的)
本文要谈的和GEQO没什么关系, 主要和CBO相关.
当PostgreSQL使用CBO时, 就一定能每次都输出最优的执行计划吗?
1. 首选我们看看CBO考察了哪些因素, 它是如何计算成本的?
成本和扫描方式, 关联方式, 操作符, 成本因子, 数据集等都有关, 具体的计算方法可参考代码:
src/backend/optimizer/path/costsize.c
我们这里简单的列举一下, 哪些因素会影响成本计算的结果, 具体算法见costsize.c :
  --  表有多少个数据块, 影响扫描数据块的成本; 例如全表扫描, 索引扫描, 都需要扫描数据块.
  --  表有多少条记录, 影响全表扫描的 CPU处理记录的COST.
  --  成本因子, 影响成本的计算结果; 例如连续或随机扫描单个数据块的成本因子, CPU从HEAP块处理一条记录的成本因子, 从INDEX块处理一条索引记录的成本因子, 执行一个操作符或函数的成本因子.
  --  内存大小, 影响索引扫描的计算成本.
  --  数据存储物理顺序和索引顺序的离散度, 影响索引扫描的计算成本. 
  --  列统计信息(列宽, 空值比例, 唯一值比例, 高频值及其比例, bucket, 物理顺序和索引顺序的离散度, 数组的话还有数组的统计信息, 等), 影响选择性, 即结果集行数, 最终影响索引扫描的计算成本.
  --  创建函数或操作符时设置的成本.

2. 然后我们看看哪些因素CBO没有考虑进去, 还有哪些因素CBO考虑进去了, 但是可能会随时发生变化的. 
 PostgreSQL是否能动态的跟上这些变化?
2.1 PostgreSQL开启自动analyze, 可以适时更新的因素如下 : 
  --  表有多少数据块, 记录数, 更新pg_class.relpages, pg_class.reltuples
  --  列统计信息, 数据存储物理顺序和索引顺序的离散度, 更新pg_statistic
2.2 静态配置因素 : 
  --  实际可用作缓存的内存, 因为数据库所在的操作系统中可能还运行了其他程序, 可用作缓存的内存可能会发生变化. 即使没有运行其他程序, 当数据库会话中有大量使用了work_mem时, 也会造成可用做缓存的内存发生变化.
  --  创建函数或操作符时设置的成本, 当函数因为内部SQL或处理逻辑等变化, 可能导致函数本身的处理时间发生变化. 
2.3 未考虑的因素 : 
  --  块设备的的预读, 一般情况下一次读取时, 会预读128KB的数据. 
      # blockdev --getra /dev/sda
      256
      这有什么影响呢? 如果你要读取的数据在连续的128KB数据块中, 那么之需要一次块设备的IO. 对数据库来说, 扫描数据时扫多少个数据块可不管这个, 都会计算成本, 因此对于不同的块设备预读配置, 或者对于不同的块设备(如机械盘和SSD), 扫描成本可能不一样. PostgreSQL块设备的性能反映在成本计算方面, 就是seq_page_cost, random_page_cost.
      这两个参数可以针对表空间设置, 也就是说, 不同的表空间, 可以设置不同的值, 例如我们有在SSD建立的表空间, 也有在普通机械盘上创建的表空间, 当然需要设置不同的seq_page_cost, random_page_cost值.
      但是对于预读, 如果发生变更, 对实际的性能有细微的影响, 一般应该不会一天到晚变更块设备的read ahead吧.
2.4 generic plan cache, 即执行计划缓存.
      PostgreSQL 通过choose_custom_plan选择重新规划执行计划还是使用缓存的执行计划, 当cached plan成本大于custom的平均成本时, 会选择custom plan , 所以当统计信息正确的情况下, 可以及时发现缓存执行计划的问题并及时规划新的执行计划.
      详见 :  src/backend/utils/cache/plancache.c
2.5 采样精度参数default_statistics_target , 影响bucket个数, 采样的精度.

经过一番分析, PostgreSQL使用了CBO, 就一定能"每次"都输出最优的执行计划吗?
1. 首选要确保人为设置成本因子准确, 另外需要打开自动analyze(适时更新 列统计信息, 块, 离散度等), 
2. 影响成本的因素还有一些是静态配置的 : 如可用作BUFFER的内存, 函数的成本.
3. 还有没考虑的: 预读 (甚微).
大多数情况下, 如果我们设置了合理的配置, 很少需要使用hint的. 除了以上2,3提到的两点. 
同时hint也有比较严重的弊端, 如果将hint写在程序代码中, 一旦需要变更执行计划, 还需要改程序代码, 不灵活.

当然, 我们不排除另一种用HINT的出发点, 例如调试. 我就想看看不同执行计划下执行效率是否和想象的一样.
(我们也可以使用开关来控制执行计划, 但是有HINT不是更直接一点嘛)

长远来看, 如果仅仅从性能角度来将, 不断改进数据库本身的优化器是比较靠谱的. 但是对于例如调试这样的需求, 有HINT更方便也是对的.

好了还是进入主题, 大多数Oracle用户在接触到PostgreSQL后, 会问PG有没有SQL hint?
为了让数据库按照用户的想法输出执行计划, 一般来说PostgreSQL提供了一些开关, 如关闭全表扫描, 让它去走索引.
关闭索引扫描, 让它去走bitmap或全表扫描, 关闭嵌套循环, 让他去走hash join或merge join等.
但是仅仅有这些开关, 还不是非常的好用, 到底有没有直接点的HINT呢?
有一个插件可以解决你的问题, pg_hint_plan.
pg_hint_plan利用PostgreSQL 开放的hook接口, 所以不需要改PG代码就实现了注入HINT的功能.
 
 

/*
 * Module load callbacks
 */
void
_PG_init(void)
{
...
}


由于不同PostgreSQL 版本, plan部分的代码可能不一致, 所以pg_hint_plan也是分版本发布的源码.
例如我要在PostgreSQL 9.4.1中测试一下这个工具.

接下来测试一下 : 
安装
 
 

# wget http://iij.dl.sourceforge.jp/pghintplan/62456/pg_hint_plan94-1.1.3.tar.gz
# tar -zxvf pg_hint_plan94-1.1.3.tar.gz
# cd pg_hint_plan94-1.1.3
[root@db-172-16-3-150 pg_hint_plan94-1.1.3]# export PATH=/opt/pgsql/bin:$PATH
[root@db-172-16-3-150 pg_hint_plan94-1.1.3]# which psql
/opt/pgsql/bin/psql
[root@db-172-16-3-150 pg_hint_plan94-1.1.3]# psql -V
psql (PostgreSQL) 9.4.1
# gmake clean
# gmake
# gmake install

[root@db-172-16-3-150 pg_hint_plan94-1.1.3]# ll -rt /opt/pgsql/lib|tail -n 1
-rwxr-xr-x 1 root root  78K Feb 18 09:31 pg_hint_plan.so

[root@db-172-16-3-150 pg_hint_plan94-1.1.3]# su - postgres

$ vi $PGDATA/postgresql.conf
shared_preload_libraries = 'pg_hint_plan'
pg_hint_plan.enable_hint = on
pg_hint_plan.debug_print = on
pg_hint_plan.message_level = log

$ pg_ctl restart -m fast

postgres@db-172-16-3-150-> psql
psql (9.4.1)
Type "help" for help.
postgres=# create extension pg_hint_plan;
CREATE EXTENSION


用法举例 : 
 
 

postgres=# create table a(id int primary key, info text, crt_time timestamp);
CREATE TABLE
postgres=# create table b(id int primary key, info text, crt_time timestamp);
CREATE TABLE
postgres=# insert into a select generate_series(1,100000), 'a_'||md5(random()::text), clock_timestamp();
INSERT 0 100000
postgres=# insert into b select generate_series(1,100000), 'b_'||md5(random()::text), clock_timestamp();
INSERT 0 100000
postgres=# analyze a;
ANALYZE
postgres=# analyze b;
ANALYZE

postgres=# explain select a.*,b.* from a,b where a.id=b.id and a.id<10;
                              QUERY PLAN                               
-----------------------------------------------------------------------
 Nested Loop  (cost=0.58..83.35 rows=9 width=94)
   ->  Index Scan using a_pkey on a  (cost=0.29..8.45 rows=9 width=47)
         Index Cond: (id < 10)
   ->  Index Scan using b_pkey on b  (cost=0.29..8.31 rows=1 width=47)
         Index Cond: (id = a.id)
(5 rows)
在没有pg_hint_plan时, 我们需要使用开关来改变PostgreSQL的执行计划
postgres=# set enable_nestloop=off;
SET
postgres=# explain select a.*,b.* from a,b where a.id=b.id and a.id<10;
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
 Hash Join  (cost=8.56..1616.65 rows=9 width=94)
   Hash Cond: (b.id = a.id)
   ->  Seq Scan on b  (cost=0.00..1233.00 rows=100000 width=47)
   ->  Hash  (cost=8.45..8.45 rows=9 width=47)
         ->  Index Scan using a_pkey on a  (cost=0.29..8.45 rows=9 width=47)
               Index Cond: (id < 10)
(6 rows)

postgres=# set enable_nestloop=on;
SET
postgres=# explain select a.*,b.* from a,b where a.id=b.id and a.id<10;
                              QUERY PLAN                               
-----------------------------------------------------------------------
 Nested Loop  (cost=0.58..83.35 rows=9 width=94)
   ->  Index Scan using a_pkey on a  (cost=0.29..8.45 rows=9 width=47)
         Index Cond: (id < 10)
   ->  Index Scan using b_pkey on b  (cost=0.29..8.31 rows=1 width=47)
         Index Cond: (id = a.id)
(5 rows)

使用pg_hint_plan来改变PostgreSQL的执行计划 : 
 
 

postgres=# /*+                                 
  HashJoin(a b)
  SeqScan(b)
*/ explain select a.*,b.* from a,b where a.id=b.id and a.id<10;
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
 Hash Join  (cost=8.56..1616.65 rows=9 width=94)
   Hash Cond: (b.id = a.id)
   ->  Seq Scan on b  (cost=0.00..1233.00 rows=100000 width=47)
   ->  Hash  (cost=8.45..8.45 rows=9 width=47)
         ->  Index Scan using a_pkey on a  (cost=0.29..8.45 rows=9 width=47)
               Index Cond: (id < 10)
(6 rows)

postgres=# /*+ SeqScan(a) */ explain select * from a where id<10;
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan on a  (cost=0.00..1483.00 rows=10 width=47)
   Filter: (id < 10)
(2 rows)

postgres=# /*+ BitmapScan(a) */ explain select * from a where id<10;
                             QUERY PLAN                              
---------------------------------------------------------------------
 Bitmap Heap Scan on a  (cost=4.36..35.17 rows=9 width=47)
   Recheck Cond: (id < 10)
   ->  Bitmap Index Scan on a_pkey  (cost=0.00..4.36 rows=9 width=0)
         Index Cond: (id < 10)
(4 rows)


目前pg_hint_plan支持的HINT

The available hints are listed below.

Group Format Description
Scan method SeqScan(table) Forces sequential scan on the table
TidScan(table) Forces TID scan on the table.
IndexScan(table[ index...]) Forces index scan on the table. Restricts to specified indexes if any.
IndexOnlyScan(table[ index...]) Forces index only scan on the table. Rstricts to specfied indexes if any. Index scan may be used if index only scan is not available. Available for PostgreSQL 9.2 and later.
BitmapScan(table[ index...]) Forces bitmap scan on the table. Restoricts to specfied indexes if any.
NoSeqScan(table) Forces not to do sequential scan on the table.
NoTidScan(table) Forces not to do TID scan on the table.
NoIndexScan(table) Forces not to do index scan and index only scan (For PostgreSQL 9.2 and later) on the table.
NoIndexOnlyScan(table) Forces not to do index only scan on the table. Available for PostgreSQL 9.2 and later.
NoBitmapScan(table) Forces not to do bitmap scan on the table.
Join method NestLoop(table table[ table...]) Forces nested loop for the joins consist of the specifiled tables.
HashJoin(table table[ table...]) Forces hash join for the joins consist of the specifiled tables.
MergeJoin(table table[ table...]) Forces merge join for the joins consist of the specifiled tables.
NoNestLoop(table table[ table...]) Forces not to do nested loop for the joins consist of the specifiled tables.
NoHashJoin(table table[ table...]) Forces not to do hash join for the joins consist of the specifiled tables.
NoMergeJoin(table table[ table...]) Forces not to do merge join for the joins consist of the specifiled tables.
Join order Leading(table table[ table...]) Forces join order as specified.
Leading(<join pair>) Forces join order and directions as specified. A join pair is a pair of tables and/or other join pairs enclosed by parentheses, which can make a nested structure.
Row number correction Rows(table table[ table...] correction) Corrects row number of a result of the joins consist of the specfied tables. The available correction methods are absolute (#<n>), addition (+<n>), subtract (-<n>) and multiplication (*<n>). <n> should be a string that strtod() can read.
GUC Set(GUC-param value) Set the GUC parameter to the value while planner is running.

[参考]
5. src/backend/utils/cache/plancache.c
6. src/backend/optimizer/path/costsize.c
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
6月前
|
SQL 人工智能 关系型数据库
PostgreSQL 常用SQL(持续更新...)
PostgreSQL 常用SQL(持续更新...)
|
6月前
|
SQL 关系型数据库 数据库
实时计算 Flink版操作报错之使用SQL 将 PostgreSQL 的 date 类型字段转换为 TIMESTAMP 类型时遇到报错,该如何处理
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
2月前
|
SQL 关系型数据库 C语言
PostgreSQL SQL扩展 ---- C语言函数(三)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
3月前
|
SQL 存储 关系型数据库
PostgreSQL核心之SQL基础学习
PostgreSQL核心之SQL基础学习
47 3
|
3月前
|
SQL 安全 关系型数据库
PostgreSQL SQL注入漏洞(CVE-2018-10915)--处理
【8月更文挑战第8天】漏洞描述:PostgreSQL是一款自由的对象关系型数据库管理系统,支持多种SQL标准及特性。存在SQL注入漏洞,源于应用未有效验证外部输入的SQL语句,允许攻击者执行非法命令。受影响版本包括10.5及更早版本等。解决方法为升级PostgreSQL
275 2
|
3月前
|
SQL 关系型数据库 MySQL
SQL Server、MySQL、PostgreSQL:主流数据库SQL语法异同比较——深入探讨数据类型、分页查询、表创建与数据插入、函数和索引等关键语法差异,为跨数据库开发提供实用指导
【8月更文挑战第31天】SQL Server、MySQL和PostgreSQL是当今最流行的关系型数据库管理系统,均使用SQL作为查询语言,但在语法和功能实现上存在差异。本文将比较它们在数据类型、分页查询、创建和插入数据以及函数和索引等方面的异同,帮助开发者更好地理解和使用这些数据库。尽管它们共用SQL语言,但每个系统都有独特的语法规则,了解这些差异有助于提升开发效率和项目成功率。
404 0
|
3月前
|
SQL 存储 流计算
Flink SQL 在快手实践问题之表示 Mini-Batch hint如何解决
Flink SQL 在快手实践问题之表示 Mini-Batch hint如何解决
37 0
|
4月前
|
SQL Oracle 关系型数据库
|
5月前
|
SQL 运维 安全
数据管理DMS产品使用合集之执行SQL时,如何添加Hint来改变查询的执行计划
阿里云数据管理DMS提供了全面的数据管理、数据库运维、数据安全、数据迁移与同步等功能,助力企业高效、安全地进行数据库管理和运维工作。以下是DMS产品使用合集的详细介绍。
60 1
|
5月前
|
SQL 关系型数据库 数据库
nacos 2.2.3版本 查看配置文件的历史版本的接口 是针对MySQL数据库的sql 改成postgresql后 sql语句报错 该怎么解决
在Nacos 2.2.3中切换到PostgreSQL后,执行配置文件历史版本分页查询出错,因`LIMIT 0, 10`语法不被PostgreSQL支持,需改为`LIMIT 10 OFFSET 0`。仅当存在历史版本时报错。解决方案是调整查询SQL以兼容PostgreSQL语法。

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版
  • 下一篇
    无影云桌面