PgSQL · 源码分析 · PG优化器物理查询优化

在之前的一篇月报中,我们已经简单地分析过PG的优化器(PgSQL · 源码分析 · PG优化器浅析),着重分析了SQL逻辑优化,也就是尽量对SQL进行等价或者推倒变换,以达到更有效率的执行计划。本次月报将会深入分析PG优化器原理,着重物理查询优化,包括表的扫描方式选择、多表组合方式、多表组合顺序等。

表扫描方式

表扫描方式主要包含顺序扫描、索引扫描以及Tid扫描等方式,不同的扫描方式

  • Seq scan,顺序扫描物理数据页
postgres=> explain select * from t1 ;
                     QUERY PLAN
-----------------------------------------------------
 Seq Scan on t1  (cost=0.00..14.52 rows=952 width=8)
  • Index scan,先通过索引值获得物理数据的位置,再到物理页读取
postgres=> explain select * from t1 where a1 = 10;
                             QUERY PLAN
--------------------------------------------------------------------
 Index Scan using t1_a1_key on t1  (cost=0.28..8.29 rows=1 width=8)
   Index Cond: (a1 = 10)
  • Tid scan,通过page号和item号直接定位到物理数据
postgres=> explain select * from t1 where ctid='(1,10)';
                    QUERY PLAN
--------------------------------------------------
 Tid Scan on t1  (cost=0.00..4.01 rows=1 width=8)
   TID Cond: (ctid = '(1,10)'::tid)

选择度计算

  • 全表扫描选择度计算

全表扫描时每条记录都会返回,所以选择度为1,所以rows=10000

EXPLAIN SELECT * FROM tenk1;

                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

 SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';

 relpages | reltuples
----------+-----------
      358 |     10000
  • 整型大于或者小于选择度计算
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000;

                                   QUERY PLAN
--------------------------------------------------------------------------------
 Bitmap Heap Scan on tenk1  (cost=24.06..394.64 rows=1007 width=244)
   Recheck Cond: (unique1 < 1000)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..23.80 rows=1007 width=0)
         Index Cond: (unique1 < 1000)

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='unique1';

                   histogram_bounds
------------------------------------------------------
 {0,993,1997,3050,4040,5036,5957,7057,8029,9016,9995}
selectivity = (1 + (1000 - bucket[2].min)/(bucket[2].max - bucket[2].min))/num_buckets
            = (1 + (1000 - 993)/(1997 - 993))/10
            = 0.100697
rows = rel_cardinality * selectivity
     = 10000 * 0.100697
     = 1007  (rounding off)
  • 字符串等值选择度计算
EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'CRAAAA';

                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=30 width=244)
   Filter: (stringu1 = 'CRAAAA'::name)
SELECT null_frac, n_distinct, most_common_vals, most_common_freqs FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';
null_frac         | 0
n_distinct        | 676
most_common_vals|{EJAAAA,BBAAAA,CRAAAA,FCAAAA,FEAAAA,GSAAAA,JOAAAA,MCAAAA,NAAAAA,WGAAAA}
most_common_freqs | {0.00333333,0.003,0.003,0.003,0.003,0.003,0.003,0.003,0.003,0.003}
selectivity = mcf[3]
            = 0.003
rows = 10000 * 0.003
     = 30

备注:如果值不在most_common_vals里面,计算公式为:

selectivity = (1 - sum(mvf))/(num_distinct - num_mcv)
  • cost计算

代价模型:总代价=CPU代价+IO代价+启动代价

postgres=> explain select * from t1 where a1 > 10;
                     QUERY PLAN
-----------------------------------------------------
 Seq Scan on t1  (cost=0.00..16.90 rows=942 width=8)
   Filter: (a1 > 10)
(2 rows)
其中:
postgres=> select relpages, reltuples from pg_class where relname = 't1';
 relpages | reltuples
----------+-----------
        5 |       952
(1 row)
cpu_operator_cost=0.0025
cpu_tuple_cost=0.01
seq_page_cost=1
random_page_cost=4

总cost = cpu_tuple_cost * 952 + seq_page_cost * 5 + cpu_operator_cost * 952
= 16.90
其他扫描方式cost计算可以参考如下函数:

postgres=> select amcostestimate,amname from pg_am ;
  amcostestimate  | amname
------------------+--------
 btcostestimate   | btree
 hashcostestimate | hash
 gistcostestimate | gist
 gincostestimate  | gin
 spgcostestimate  | spgist
(5 rows)

表组合方式

  • Nest Loop

SELECT  * FROM     t1 L, t2 R WHERE  L.id=R.id

假设:

M = 20000 pages in L, pL = 40 rows per page,
N = 400 pages in R, pR = 20 rows per page.

select relpages, reltuples from pg_class where relname=‘t1’

L和R进行join

for l in L do
  for r in R do
    if rid == lid  then ret += (r, s)

对于外表L每一个元组扫描内表R所有的元组
总IO代价: M + (pL * M) * N = 20000 + (4020000)400
= 320020000

  • MergeJoin

主要分为3步:

(1) Sort L on lid 代价MlogM

(2) Sort R on rid 代价NlogN

(3) Merge the sorted L and R on lid and rid 代价M+N

  • HashJoin

使用HashJoin的前提是其中假设一个表可以完全放在内存中,实际过程中可能统计信息有偏差,优化器认为一个表可以放到内存中,事实上数据在内存中放不下,需要使用临时文件,这样会降低性能。

表的组合顺序

不同的组合顺序将会产生不同的代价,想要获得最佳的组合顺序,如果枚举所有组合顺序,那么将会有N!的排列组合,计算量对于优化器来说难以承受。PG优化器使用两种算法计算更优的组合顺序,动态规划和遗传算法。对于连接比较少的情况使用动态规划,否则使用遗传算法。

  • 动态规划求解过程

PG优化器主要考虑将执行计划树生成以下三种形式:

动态规划的思想可以参考百度百科动态规划,主要将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。具体应用在表组合顺序上,则是先考虑单表最优访问访问,然后考虑两种组合,再考虑多表组合,最终得到更优的解。

时间: 2024-07-30 13:06:01

PgSQL · 源码分析 · PG优化器物理查询优化的相关文章

PgSQL · 源码分析 · PG优化器浅析

在使用PostgreSQL数据库过程中,对SQL调优最常用的手段是使用explain查看执行计划,很多时候我们只关注了执行计划的结果而未深入了解执行计划是如何生成的.优化器作为数据库核心功能之一,也是数据库的"大脑",理解优化器将有助于我们更好地优化SQL,下面将会为大家解开PostgreSQL优化器神秘的面纱. SQL执行过程 在PG数据库中,对于DDL语句无需进行优化,到utility模块处理,对于DML语句需要到优化器中处理,一个用户连接从接收SQL到执行的流程如下: 查询重写

PgSQL · 源码分析 · PG 优化器中的pathkey与索引在排序时的使用

概要 SQL在PostgreSQL中的处理,是类似于流水线方式的处理,先后由: 词法.语法解析,生成解析树后,将其交给语义解析 语义解析,生成查询树,将其交给Planner Planner根据查询树,生成执行计划,交给执行器 执行器执行完成后返回结果 数据库优化器在生成执行计划的时候,优化器会考虑是否需要使用索引,而使用了索引之后,则会考虑如何利用索引已经排过序的特点,来优化相关的排序,比如ORDER BY / GROUP BY等. 先来看个索引对ORDER BY起作用的例子: postgres

PgSQL · 源码分析 · PG中的无锁算法和原子操作应用一则

原子操作概述 近年来随着服务器上CPU核数的不断增加,无锁算法(Lock Free)越来越广泛的被应用于高并发的系统中.PostgreSQL 做为世界上最高级开源数据库也在9.5时引入了无锁算法.本文先介绍了无锁算法和原子操作在PostgreSQL中的具体实现, 再通过一个Patch来看一下在PostgreSQL中是如何利用它来解决实际的高并发问题的. 无锁算法是利用CPU的原子操作实现的数据结构和算法来解决原来只能用锁才能解决的并发控制问题. 众所周知,在一个并发系统中特别是高并发的场景下,锁

PgSQL · 源码分析 · 优化器逻辑推理

背景知识 数据库优化器需要具备逻辑推理能力,而且越强越好,为什么呢? 举一些例子, 通过已知的一个人讲的是真话,推理另一个人讲的一定是真话或一定是假话. 例子1: 假设预先提供了 a > 10 是真话 可以推理出 a < 1 一定是假话 例子2: 假设预先提供了 a > 10 是真话 无法推理出 a < 100 一定是真话或假话 例子3: 假设预先提供了 a 是空 是真话 可以推理出 a 不是空 一定是假话 例子4: 假设预先提供了 a <>100 是真话 可以推理出

PgSQL · 源码分析· pg_dump分析

PostgreSQL本身提供了逻辑导出工具pg_dumpall和pg_dump,其中pg_dumpall导出所有的数据库,pg_dump导出单个数据库,两个工具的用法和参数不再详细介绍,本文从代码层面上对此过程进行分析. 概括地说,逻辑导出要干的事情就是连接对应数据库,读出各个数据库对象的定义和数据,此外还包括comment.服务器配置和权限控制等等,这些数据库对象定义的SQL语句会被写入到对应的dump文件中.其中可以设置只导出模式或者只导出数据,默认是导出模式和数据,这样就可以支持分步导出和

PgSQL · 源码分析 · AutoVacuum机制之autovacuum launcher

背景 根据之前月报的分析,PostgreSQL中的MVCC机制(详见月报)同时存储新旧版本的元组,对于经常更新的表来说,会造成表膨胀的情况.为了解决这个问题,PostgreSQL 引入了VACUUM和ANALYZE命令,并且引入了AutoVacuum自动清理. 在PostgreSQL中,AutoVacuum自动清理操作包括: 删除或重用无效元组的磁盘空间 更新数据统计信息,保证执行计划更优 更新visibility map,加速index-only scans (详见文档) 避免XID 回卷造成

《深入理解SPARK:核心思想与源码分析》一书正式出版上市

自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售,欢迎感兴趣的同学购买.我开始研究源码时的Spark版本是1.2.0,经过7个多月的研究和出版社近4个月的流程,Spark自身的版本迭代也很快,如今最新已经是1.6.0.目前市面上另外2本源码研究的Spark书籍的版本分别是0.9.0版本和1.2.0版本,看来这些书的作者都与我一样,遇到了这种问题.由于研究和

深入理解Spark:核心思想与源码分析

大数据技术丛书 深入理解Spark:核心思想与源码分析 耿嘉安 著 图书在版编目(CIP)数据 深入理解Spark:核心思想与源码分析/耿嘉安著. -北京:机械工业出版社,2015.12 (大数据技术丛书) ISBN 978-7-111-52234-8 I. 深- II.耿- III.数据处理软件 IV. TP274 中国版本图书馆CIP数据核字(2015)第280808号 深入理解Spark:核心思想与源码分析 出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037)

memcached客户端源码分析

转载:memcached客户端源码分析 memcached的Java客户端有好几种,http://code.google.com/p/memcached/wiki/Clients 罗列了以下几种 Html代码   spymemcached          * http://www.couchbase.org/code/couchbase/java             o An improved Java API maintained by Matt Ingenthron and other