本文来自:http://www.023dns.com/Database_mssql/5974.html

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;< p="">

  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;< p="">

  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;< p="">

  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;< p="">

  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;< p="">

  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;< p="">

  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

  http://pghintplan.sourceforge.jp/hint_list.html

  The available hints are listed below.

  

  

  

  [参考]

  1. http://pghintplan.sourceforge.jp/pg_hint_plan-en.html

  2. http://pghintplan.sourceforge.jp/pg_hint_plan.html

  3. http://pghintplan.sourceforge.jp/hint_list.html

  4. http://pghintplan.sourceforge.jp/

  5. src/backend/optimizer/path/costsize.c

  6. src/backend/utils/cache/plancache.c

PostgreSQL SQL HINT的使用说明的更多相关文章

  1. hint.css使用说明

    GitHub:http://liu12fei08fei.github.io/html/1hint.html hint.css使用说明 用途 快速实现tooltips提示样式 相关资源 官方网站GitH ...

  2. PostgreSql sql shell win10 下乱码解决

    重现步骤: 打开 SQL Shell (psql) Server [localhost]: Database [postgres]: Port ]: Username [postgres]: psql ...

  3. PostgreSQL SQL优化之NOT IN问题

    在我们平时写SQL时,如果遇到需要排除某些数据时,往往使用id <> xxx and id <> xxx,进而改进为id not in (xxx, xxx); 这样写没有问题, ...

  4. sql: postgreSQL sql script

    SELECT * from pg_class c,pg_attribute a,pg_type t where c.relname='BookKindList' and a.attnum>0 a ...

  5. postgresql sql查询结果添加序号列与每组第一个序号应用

    1.postgresql 查询每组第一个 ROW_NUMBER () OVER (partition by 字段 ORDER BY  字段  DESC) 写法:SELECT  ROW_NUMBER ( ...

  6. sql 连接的使用说明

    SQL中的left outer join,inner join,right outer join用法详解 使用关系代数合并数据 关系代数 合并数据集合的理论基础是关系代数,它是由E.F.Codd于19 ...

  7. postgresql sql修改表,表字段

    1.更改表名 alter table 表名 rename to 新表名 2.更改字段名 alter table 表名 rename 字段名 to 新字段名 3.增加列 ALTER TABLE ud_w ...

  8. postgresql sql语句 更改表名

    SELECT'alter table "public"."'|| t.tablename||'"'||' rename to "'|| "l ...

  9. Citus 分布式 PostgreSQL 集群 - SQL Reference(SQL支持和变通方案)

    由于 Citus 通过扩展 PostgreSQL 提供分布式功能,因此它与 PostgreSQL 结构兼容.这意味着用户可以使用丰富且可扩展的 PostgreSQL 生态系统附带的工具和功能来处理使用 ...

随机推荐

  1. 怎么解决禅道启动服务mysqlzt时的端口失败

    打开Windows任务管理器 查看服务是否有MySQL正在运行,停止服务 启动mysqlzt服务 重新启动禅道

  2. maven介绍(一)

    一.什么是maven Maven 翻译过来就是专家,其官网为: http://maven.apache.org/ Maven使用POM (Project Object Model)项目对象模型,就是通 ...

  3. socket缓冲区以及阻塞模式(七)

    一.socket缓冲区 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区. 以下用write()/send()表示写数据/发送数据,read()/recv() 表示读数据/接收 ...

  4. centos7 扩容

    用 df -h, 看到磁盘满了 (注意:如果是二,或者第三次扩容,下面的参数就不一样) fdisk /dev/sda 但是失败了,显示没有多余的分区 可以看到 有个/dev/sda4 是我在这之前刚刚 ...

  5. JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)

    this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...

  6. 前端Vue项目——课程详情页面实现

    一.详情页面路由跳转 应用 Vue Router 编程式导航通过 this.$router.push() 来实现路由跳转. 1.绑定查看详情事件 修改 src/components/Course/Co ...

  7. WPF CoboxItem控件使用SelectedItem去调System.Windows.Controls.ComboBoxItem: 前缀方法

    textComBox.SelectedItem as ComboBoxItem).Content textConbox: 控件Combobox 的Name 在Combobox控件SelectionCh ...

  8. .NET Core创建Worker Services

    .NET CORE 3.0新增了Worker Services的新项目模板,可以编写长时间运行的后台服务,并且能轻松的部署成windows服务或linux守护程序.如果安装的vs2019是中文版本,W ...

  9. Vue.js项目中使用iconfont冲突问题解决

    在开发前端项目中,字体图标变得越来越常用.一方面因为它比图片使用起来方便,可以像字体一样修改大小和颜色:另一方面是因为它可以减少请求数量,优化前端性能. iconfont的使用方法很简单,主要由三种引 ...

  10. 目标检测 <二> TensorFlow安装

    一:创建TensorFlow工作环境目录 1. 在anconda安装目录下找到envs目录然后进入 2. 在当前目录下创建一个文件夹改名为tensorflow 二: 创建TensorFlow工作环境 ...