一、执行计划的解释

1.explain 命令

语法:

EXPLAIN [ ( option [, ...] ) ] statement
EXPLAIN [ ANALYZE ] [ VERBOSE ] statement

option的选项值如下:

    ANALYZE [ boolean ]
    VERBOSE [ boolean ]
    COSTS [ boolean ]
    SETTINGS [ boolean ]
    BUFFERS [ boolean ]
    TIMING [ boolean ]
    SUMMARY [ boolean ]
    FORMAT { TEXT | XML | JSON | YAML }

1.ANALYZE:
ANALYZE选项通过实际执行的SQL来获得相应的执行计划。因为是真正的执行,所以可以看到执行计划的每一步所花费的时间以及它实际返回的行数。

PS:加上 ANALYZE 选项是真实的执行了SQL语句,对于插入、更新、删除或 CREATE TABLE AS 语句会真正的修改数据库,为了不影响真实数据,可以将 EXPLAIN ANALYZE 放到一个事务中,执行完后进行事务回滚,保证源数据不变:
BEGIN;
EXPLAIN ANALYZE …;
ROLLBACK;
END;

2.VERBOSE:
VERBOSE选项用于显示计划的附加信息。这些附加信息包括:计划树种每个节点输出的各个列,如果触发器触发,还会输出触发器的名称。该选项值默认为FALSE。

3.COSTS:
选项显示每个计划节点的启动成本和总成本,以及估计行数和每行宽度。该选项默认为TRUE。

4.BUFFERS:
BUFFERS选项显示关于缓冲区使用的信息。该参数只能与 ANALYZE 参数一起使用。显示的缓冲区信息包括共享块、本地块和临时块读和写的块数。共享块、本地块和临时块分别包含了表和索引、临时表和临时索引,以及在排序和物化计划中使用的磁盘块。上层节点显示出来的块数包括其所有子节点使用的块数。该选项值默认为FALSE。

5.FORMAT:
FORMAT选项指定输出格式,输出格式可以是TEXT、XML、JSON或YAML。非文本输出包含于文本输出格式相同的信息,打其他程序更容易解析。该参数值默认为TEXT。

2.explain输出结果解释

示例:

postgres=# explain select * from users;
                        QUERY PLAN                        
----------------------------------------------------------
 Seq Scan on users  (cost=0.00..11.10 rows=110 width=682)
(1 row)

1.结果中 “Seq Scan on users” 表示顺序扫描表 “users”,也就是全表扫描,即从头到尾地扫描表数据。

2.“(cost=0.00…11.10 rows=110 width=682)” 分为三部分:

a)cost=0.00…11.10 :“cost=” 后面有两个数字,中间有 “ … ” 分隔,第一个数字 “0.00” 表示启动的成本,也就是说返回第一行需要多少cost值;第二个数字 “11.10” 表示返回所有的数据的成本。

b)rows=110 : 表示会返回110行。

c)width=682 :表示每行平均宽度为682字节。

成本 “cost” 描述一个SQL执行的代价是多少,默认情况下不同的操作其 “cost” 的值如下:

a)顺序扫描一个数据块,cost值定为 1 。
b)随机扫描一个数据块,cost值定为 4 。
c)处理一个数据行的CPU,cost为 0.01 。
d)处理一个索引行的CPU,cost为 0.005 。
e)每个操作符的CPU代价为 0.0025 。

Postgresql能够根据以上操作类型,智能计算出一个SQL的执行代价,虽然计算存在偏差(对表进行分析可以大幅减小偏差),但是大多数情况可以做为参考确定。

3.explain 示例

(1)默认情况下输出的执行计划是文本格式,但也可以输出为 JSON 格式,例如:

postgres=# explain (format json) select * from users;
           QUERY PLAN            
---------------------------------
 [                              +
   {                            +
     "Plan": {                  +
       "Node Type": "Seq Scan", +
       "Parallel Aware": false, +
       "Relation Name": "users",+
       "Alias": "users",        +
       "Startup Cost": 0.00,    +
       "Total Cost": 1.03,      +
       "Plan Rows": 3,          +
       "Plan Width": 39         +
     }                          +
   }                            +
 ]
(1 row)

输出为 XML 格式,例如:

postgres=# explain (format xml) select * from users;
                        QUERY PLAN                        
----------------------------------------------------------
 <explain xmlns="http://www.postgresql.org/2009/explain">+
   <Query>                                               +
     <Plan>                                              +
       <Node-Type>Seq Scan</Node-Type>                   +
       <Parallel-Aware>false</Parallel-Aware>            +
       <Relation-Name>users</Relation-Name>              +
       <Alias>users</Alias>                              +
       <Startup-Cost>0.00</Startup-Cost>                 +
       <Total-Cost>1.03</Total-Cost>                     +
       <Plan-Rows>3</Plan-Rows>                          +
       <Plan-Width>39</Plan-Width>                       +
     </Plan>                                             +
   </Query>                                              +
 </explain>
(1 row)

(2)加上 “ analyze ” 参数后,可通过实际执行来获得更精确的执行计划,如下:

postgres=# select * from employee ;
 id | name  | sex | age | emp_no 
----+-------+-----+-----+--------
  1 | zhang | f   |  23 |    201
  3 | anmi  | t   |  22 |    203
(2 rows)

postgres=# explain select * from employee ;
                         QUERY PLAN                          
-------------------------------------------------------------
 Seq Scan on employee  (cost=0.00..21.10 rows=1110 width=45)
(1 row)

postgres=# explain analyze select * from employee ;
                                              QUERY PLAN                                               
-------------------------------------------------------------------------------------------------------
 Seq Scan on employee  (cost=0.00..21.10 rows=1110 width=45) (actual time=0.010..0.011 rows=2 loops=1)
 Planning Time: 0.044 ms
 Execution Time: 0.032 ms
(3 rows)

以上语句看出,表employee中实际数据只有2条,未加关键词 analyze 时的估算的执行计划和加上之后的统计计划中数据对比看出区别。

(3)只想查询执行路径的情况,不需要查看 cost 值,可以通过加 “(costs false)” 选项来实现:

postgres=# explain (costs false) select * from users;
    QUERY PLAN     
-------------------
 Seq Scan on users
(1 row)

(4)可以通过 analyze 选项和 buffers 选项,实际执行语句来查看真实的消耗的代价和缓冲区命中情况:

postgres=# explain (analyze true,buffers true) select * from users;
                                           QUERY PLAN                                           
------------------------------------------------------------------------------------------------
 Seq Scan on users  (cost=0.00..1.03 rows=3 width=39) (actual time=0.016..0.017 rows=3 loops=1)
   Buffers: shared hit=1
 Planning Time: 0.041 ms
 Execution Time: 0.033 ms
(4 rows)

加了 buffers 选项,执行计划的结果中就会出现 “Buffers: shared hit=1”,这行中的 “shared hit=1” 表示在共享内存中直接读取到了 1行,有时也会有 read和written,因为select查询语句读取的有时会把buffers中的脏块挤出去,就会产生写操作。

4.表数据读取方式

(1)全表扫描

全表扫描在Postgresql中也称为顺序扫描(seq scan),全表扫描就是把表中所有数据块从头到尾扫描一遍,然后从数据块中找到符合条件的数据块。

全表扫描在 explain 执行计划结果中用 “Seq Scan” 表示,例如:

postgres=# explain select * from employee ;
                         QUERY PLAN                          
-------------------------------------------------------------
 Seq Scan on employee  (cost=0.00..21.10 rows=1110 width=45)
(1 row)

2.索引扫描

索引通常是为了加快查询数据的速度增加的。索引扫描就是在索引中找到需要的数据行的位置,然后在到表的数据块中把相应的数据读取出来的过程。

索引扫描在 explain 执行计划结果中用 “Index Scan” 表示,例如:

postgres=# explain analyze select * from sales where product_no='100';
                                                    QUERY PLAN                                                     
-------------------------------------------------------------------------------------------------------------------
 Index Scan using sales_pkey on sales  (cost=0.14..8.16 rows=1 width=35) (actual time=0.069..0.070 rows=1 loops=1)
   Index Cond: (product_no = 100)
 Planning Time: 0.482 ms
 Execution Time: 0.090 ms
(4 rows)

3.位图扫描

位图扫描也是走索引的一种方式。方法是扫描索引,把满足条件的行或者块在内存中创建一个位图,扫描完索引后,再根据问题到表的数据文件中吧相应的数据读出来。如果走了两个索引,可以把两个索引形成的位图进行 “and” 或 “or” 计算,合并成一个位图,再到表的数据文件中读取数据。

当执行计划结果行数很多事会进行这种扫描,如非等值查询,IN 子句或多个条件都可以走不同索引时。

例如(非等值查询):

postgres=# explain analyze select * from salse_temp where product_n0<'101';
                                                      QUERY PLAN                                                      
 
----------------------------------------------------------------------------------------------------------------------
-
 Bitmap Heap Scan on salse_temp  (cost=4.29..8.85 rows=2 width=35) (actual time=0.015..0.018 rows=3 loops=1)
   Recheck Cond: (product_in < 101)
   Heap Blocks: exact=2
   ->  Bitmap Index Scan on idx_sales_temp  (cost=0.00..4.29 rows=2 width=0) (actual time=0.010..0.011 rows=3 loops=1)
         Index Cond: (product_in < 101)
 Planning Time: 0.059 ms
 Execution Time: 0.037 ms
(7 rows)

在位图扫描中可以看到,“Bitmap Index Scan” 先从索引中找到符合条件的行,然后在内存中建立位图,之后再到表中扫描读取数据,也就看到了 “Bitmap Heap Scan” 。

而执行计划结果中的 “Recheck Cond” 就是因为多版本的原因,从索引中找到行从表读取后,还需要检查一下条件。

通过两个有索引的字段 or 的条件查询,会出现 “BitmapOr”,就表示使用了 or 操作符合并了两个索引位图:

postgres=# explain  select * from salse_temp where product_in < '101' or price <100;
                                    QUERY PLAN                                     
-----------------------------------------------------------------------------------
 Bitmap Heap Scan on salse_temp  (cost=8.58..14.28 rows=4 width=35)
   Recheck Cond: ((product_in < 101) OR (price < '100'::numeric))
   ->  BitmapOr  (cost=8.58..8.58 rows=4 width=0)
         ->  Bitmap Index Scan on idx_sales_temp  (cost=0.00..4.29 rows=2 width=0)
               Index Cond: (product_in < 101)
         ->  Bitmap Index Scan on idx_sal_price  (cost=0.00..4.29 rows=2 width=0)
               Index Cond: (price < '100'::numeric)
(7 rows)

4.条件过滤

条件过滤,一般是where条件加上的过滤条件,当扫描数据行时,会找出满足过滤条件的行,条件过滤会在执行计划中显示为 “Filter”,例如

postgres=# explain select * from sales where price  < '200';
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan on sales  (cost=0.00..8.41 rows=3 width=35)
   Filter: (price < '200'::numeric)
(2 rows)

5. Nestloop Join

嵌套循环连接(Nestloop Join)是在两个表做连接时最常见的一种连接方式。在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表中检索找到与它匹配的行,因此整个产寻返回的结果集不能太大(>10000不适合),要把返回子集较小的表作为外表,而且在内表的链接字段上要有索引,否则影响查询速度。

执行过程中未,确定一个驱动表(outer table),另一个表为 inner table,驱动表中的每一行与 inner 表中的相应记录 join ,类似一个嵌套的循环。适用于驱动表的记录集比较小(< 10000)而且 inner 表有索引,需要注意,join 的顺序很重要,驱动表的就一定要笑,返回结果集的响应时间是最快的。

6. Hash Join

优化器使用两个表中较小的表,并利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。

这种方式适用于较小的表可以完全存放到内存中的情况,这样总成本就是访问两个表的成本之和。但是如果表很大,不能完全放入内存,优化器会将它分割成诺干个不同的分区,把不能放入内存的部分写入磁盘的临时段,此时要有较大的临时段来提高 I/O 的性能。

示例:

postgres=# explain analyze select a.student_name,b.chinese_score,b.math_score from student a,score b where a.student_no=b.student_no ;
                                                     QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=17.65..51.06 rows=1850 width=206) (actual time=0.023..0.027 rows=10 loops=1)
   Hash Cond: (b.student_no = a.student_no)
   ->  Seq Scan on score b  (cost=0.00..28.50 rows=1850 width=12) (actual time=0.005..0.006 rows=10 loops=1)
   ->  Hash  (cost=13.40..13.40 rows=340 width=202) (actual time=0.011..0.011 rows=10 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on student a  (cost=0.00..13.40 rows=340 width=202) (actual time=0.005..0.007 rows=10 loops=1)
 Planning Time: 0.109 ms
 Execution Time: 0.046 ms
(8 rows)

7. Merge Join

通常情况下散列连接的效果比合并连接的好,也就是 Hash Join 比 Merge Jion 效果好,但是如果源数据上有索引,或者结果已经被排过序,在执行排序合并连接时就不需要排序了,这时合并连接的性能会优于散列连接。

二、执行计划相关的参数配置

1. ENABLE_ 参数*

在Postgresql中有一些以 “enable_* ”开头的参数,这些参数提供了想象查询优化器选择不同执行计划的方法。有时,如果优化器为特定查询选择的执行计划并不是最优的,可以设置这些参数强制优化器选择一个更好的执行计划来临时解决问题,但是一般不会在Postgresql中更改这些参数值的默认值。因为通常情况下,Postgresql都不会走错执行计划。

Postgresql中的执行计划选择主要是根据统计信息的是否准确来确定。修改 ENABLE_* 这类参数只能作为临时解决问题的方法,详细参数说明:

参数名称

类型

说明

enable_seqscan

Boolean

是否选择全表顺序扫描。实际上并不能完全禁止全表扫描,但是把这个变量关闭会让优化器在存在其他方法时优先选择其他方法

enable_indexscan

Boolean

是否选择索引扫描

enable_bitmapscan

Boolean

是否选择位图扫描

enable_tidscan

Boolean

是否选择位图扫描

enable_nestloop

Boolean

多表连接时,是否选择嵌套循环连接。如果设置为 off ,执行计划只有走嵌套循环连接一条路时,优化器也会也只能选择这条路。但是如果有其他连接方式就会优先选择其他方式。

enable_hashjoin

Boolean

多表连接时,是否选择hash连接

enable_mergejoin

Boolean

多表连接时,是否选择merge连接

enable_hashagg

Boolean

是否使用hash聚合

enable_sort

Boolean

是否使用明确的排序,如果设置为 off ,执行计划只有排序一条路时,优化器也只能选择这条路,但是如果有其他方式则会优先选择其他方式

2. COST 基准值参数

执行计划在选择最优路径时,不同路径的cost值只有相对意义,同时缩放它们将不会对不同路径的选择产生任何影响。默认情况下,它们以顺序扫描一个数据块的开销作为基准单位,也就是将顺序扫描的基准参数 seq_page_cost 默认设置为 1.0 ,其他开销的基准参数都对照它来设置。从理论上来说也可以使用其他基准方法,如用以毫秒计算的实际执行时间做基准,但这些基准方法可能会更复杂。

COST基准值参数如下:

参数名称

类型

说明

seq_page_cost

float

执行计划中一次顺序访问一个数据块页面的开销,默认是1.0

random_page_cost

float

执行计划中计算随机访问一个数据块页面的开销。默认是4.0,也就是说随机访问一个数据块页面的开销是顺序访问的4倍

cpu_tuple_cost

float

执行计划中计算处理一条数据行的开销。默认值为 0.01

cpu_index_tuple_cost

float

执行计划中计算处理一条索引行的开销。默认值为 0.005

cpu_operator_cost

float

执行计划中执行一个操作符或函数的开销。默认值为 0.0025

effective_cache_size

int

执行计划中在一次索引扫描中可用的磁盘缓冲区的有效大小。在计算一个索引的预计开销值时会对这个参数加以考虑。更高的数值会导致更可能使用索引扫描,更低的数值会导致更有可能选择顺序全表扫描。这个参数对Postgresql分配的共享内存大小没有任何影响,它只用于执行计划中代价的估算。数值是用数据页来计算的,通常每个页面是8KB大小。默认是16384个数据块带下,即128MB

在上面的配置中,参数 **seq_page_cost **一般作为基准,不用改变。可能需要改变的是参数 random_page_cost,如果在读数据时,数据基本都命中在内存中,这时随机读和顺序读的差异不大,可能需要把参数 random_page_cost 的值调小。如果想让优化器偏向走索引,而不走全表扫描,可以吧 random_page_cost 调大。

3. 基因查询优化的参数

基因查询优化(GEQO)是一个使用探索式搜索来执行查询规划的算法,它可以降低负载查询的规划时间。GEQO 的检索是随机的,因此它生成的执行计划存在不确定性。

基因查询优化器相关配置参数:

参数名称

类型

说明

geqo

Boolean

允许或禁止基因查询优化器,在生产系统中最好把此参数打开,默认是打开的。geqo_threshold 参数提供了一种是否使用基因查询优化方法的更精细的控制方法。

geqo_threshold

integer

只有当涉及的from 关系数量至少有多个时,才使用基因查询优化。对于数量小于此值的查询,也许使用判定性的穷举搜索更有效。但是对于有许多表的查询,规划器做判断要花很多时间。默认是 12,请注意,一个 FULL OUTER JOIN 只能算一个 from 项

geqo_effort

integer

控制GEQO里规划时间和查询规划有效性之间的平衡。这个变量必须是一个从 1 到 10 的证书。默认为 5,。大的数值增加花在进行查询规划上面的时间,但是也很可能提高选中更有效的查询规划的几率。geqo_effort实际上并没有直接干了什么事,只是用于计算其他那些影响GEQO行为变量的默认值,如果可以,也可以手工设置其他参数

geqo_pool_size

integer

控制GEQO使用的池的大小,池大小是基因全体中的个体数量,它必须至少是 2 ,有用的数值通常在 100 到 1000 之间。如果设置为 0 (默认值),那么就会基于 geqo_effort 和查询中标的数量来选取一个合适的值。

geqo_generations

integer

控制GEQO使用的子代数目。子代的意思是算法的迭代次数。它必须至少是 1 ,有用值的安慰和池大小相同。如果设置为0(默认值),嘛呢将基于geqo_pool_size选取合适的值

geqo_selection_bias

float

控制GEQO使用的选择性偏好。选择性偏好是指在一个种群中的选择性压力。数值可以是 1.5 到 2.0 之间。默认值 2.0

geqo_seed

float

控制GEQO使用的随机数产生器的初始值,用以选择随机路径。这个值可以从 0(默认值)到 1,。修改这个值会改变连接路径搜索的设置,同时会找到最优或最差的路径

注意,在没有很多表做关联查询时,并不需要关注这些基因查询优化器的参数,因为这时基本不会走基因查询,只有当关联查询表的数目超过 geqo_threshold 配置项时,才会走基因查询优化算法。如果不清楚基因查询原理,保持上面这些参数的默认值即可。

4. 其他执行计划配置项

相关配置项:

参数名称

类型

说明

default_staistics_target

integer

此参数设置表字段的默认直方图统计目标值,如果表字段的直方图统计目标值没有用 ALTER TABLE SET STATISTICS 明确设置过,则使用此参数指定的值。此值越大,ANALYZE 需要花费的时间越多,同时统计出的直方图信息越详细,这样生成的执行计划也越准确。默认值 100 ,最大值 10000

constraint_exclusion

enum

指定在执行计划中是否使用约束排除。可以取三个值:partition、on、off 。默认值为 partition。约束排除就是指优化器分析 where 中的过滤条件和表上的 check 约束,当从语义上就能分析出不需要访问这张表时,执行计划就会直接跳过这张表。如表上的一个字段有约束 check col1 > 10000,当查询表 “select * from t where col1 < 900” 时,优化器对比约束条件,知道根本没有 col1 < 900 的记录,跳过对表的扫描直接返回0条记录。 当优化器使用约束排除时,需要花更多的时间去对比约束条件和 where 中的过滤条件,在多数情况下,对无继承的表打开约束排除意义不大,所以Postgresql把此值默认设置为 partition。当对一张表做查询时,如果这个表有很多继承的子表,通常也需要扫描这些子表,设置为 partition ,优化器就会对这些子表多约束排除分析。

cursor_tuple_fraction

float

游标在选择执行计算时有两种策略:第一种是选择总体执行代价最低的,第二种是返回第一条记录时代价最低的。有时总体执行代价最低,但返回第一条记录的代价不是最低的,这是返回给用户的第一条记录的时间比较长,会让用户觉得等待了较长的时间,系统才有响应,导致用户体验不行。为了提高用户体验,可以选择返回第一条记录最快的执行计划,这时用户就可以比较快的看到第一条记录。设置游标,在选择总体代价最低的执行计划和返回第一条记录代价最低的执行计划两者之间,比较倾向性的大小。默认值为 0.1 。最大值为 1.0 。此时游标会选择总体代价最低的执行计划,不会考虑多久才会返回第一条记录。

from_collapse_limit

integer

默认值为 8。如果查询重写生成的FROM 后的项目数不超过这个限制数目,优化器将把资产寻融合到上次查询。小的数值可缩短规划的时间,但是可能会生成差一些的查询计划。将这个值设置得与配置项 geqo_threshold 的数值相同或更大,可能触发使用 GEQO 规划器,从而产生不确定的执行计划

join_collapse_limit

integer

如果查询重写生成的 FROM 后的项目数不超过这个限制数目,优化器把显式使用 JOIN 子句(不包括 FULL JOIN )的连接也重写到 FROM 后的列表中。小的数值可缩短规划的时间,但是可能会产生差一些的查询计划值。默认值与 from_collapse_limit 一样,将这个值设置得与配置项 geqo_threshold 的数值相同或更大,可能触发使用 GEQO 规划器,从而产生不确定的执行计划

三、统计信息的收集

Postgresql 收集的统计信息主要用于查询优化时的代价估算。当然也对数据库活动监控和性能分析也有很大帮助。

表、索引的行数、块数等统计信息记录在系统表 pg_class 中,其他的统计信息主要收集在系统表 pg_statistic 中。

1.统计信息收集器的配置项

参数名称

类型

说明

track_counts

Boolean

控制是否收集表和索引上访问的统计信息。默认是打开的。

track_functions

enum

是否收集函数调用次数和时间的统计信息。可以取 none、pl、all 三个值。none 表示不收集;pl 表示只收集过程语言函数;all 表示收集所有函数,包括SQL和C语言函数。默认值 none。

track_activities

Boolean

是否允许跟踪每个session正在执行的SQL命令的信息和命令开始的时间。这些信息可以在视图 pg_stat_activity中看到。默认参数是打开的。

track_activity_query_size

integer

在pg_stat_activity视图中的query 字段做多显示多少字节,默认值为1024,超过此设置值的内容被截断。

track_io_timing

Boolean

是否允许统计IO调用的时间,默认为关闭。如果打开了此选项,在带 buffers 选项的 explain 命令中将显示 IO 调用的时间。这些IO统计信息也可以在 pg_stat_database 和 pg_stat_statements 中看到,这时Postgresql 9.2 后新增的参数。

update_process_title

Boolean

当后台服务进程正在执行命令(如一条SQL语句)时,是否更新其 title 信息。在Linux系统下参数默认是打开的。所以在Linux下,默认可以使用 ps 命令查看一个后台服务进程是否正在执行命令

stats_temp_directory

string

设置存储临时统计数据的路径,可以是一个相对于数据目录的相对路径,也可以是绝对路径。默认值为 pg_stat_tmp

2. SQL执行的统计信息输出

可以使用以下4个Boolean类型的参数来控制是否输出SQL执行过程的统计信息到日志中:
log_statement_stats
log_parser_stats
log_planner_stats
log_executor_stats
其中参数log_statement_stats控制是否输出所有SQL语句的统计信息,其他的参数控制每个SQL是否输出不同执行模块中的统计信息。

3. 手工收集统计信息

手工收集统计信息的命令是 analyze 命令,此命令收集表的统计信息,然后报结果保存在系统表 pg_statistic 里。优化器可以使用这些统计信息来确定最优的执行计划。

在默认的Postgresql 配置中,autovacuum 守护进程是打开的,它自动地分析表,并收集表的统计信息,当 autovacuum 关闭时,需要周期性的或者在表的大部分内容变更后运行 ANALYZE 命令。准确的统计信息将帮助优化器生成最优的执行计划,从而改善查询的性能。一种比较常用的策略是每天在数据库比较空闲的时候运行一次 VACUUM 和 ANALYZE。

ANALYZE 命令的格式如下:

ANALYZE [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ]

where option can be one of:

    VERBOSE [ boolean ]
    SKIP_LOCKED [ boolean ]

and table_and_columns is:

    table_name [ ( column_name [, ...] ) ]

语法说明:

1.VERBOSE :增加此选项将显示处理的进度,以及表的一些统计信息。

2.table :要分析的表的名称。如果不指定则对整个数据库中的所有表做分析。

3.column :要分析的特定字段的名称。默认分析所有字段。

示例:

1.分析users表的name字段:

analyze users(name);

2.分析表users的两个列name、age:

analyze users(name,age);

3.分析users表的所有字段:

analyze users;

ANALYZE 命令会在表上加一个读锁,因此可以和表上其他SQL并行执行。ANALYZE 会收集表中每个字段的直方图和最常用数值的列表。

对于大表,ANALYZE 只读取表的部分内容做一个随机抽样,不读取表的所有内容,这就保证了即使在很大的表上也只需很少时间就可以完成统计信息的收集。统计信息只能是近似的结果,即使表内容实际上没什么变化,运行 ANALYZE 命令后显示的explain的执行计划的cost值也会有一些小变化。为了调整所收集的统计信息的准确度,可以增大随机抽样的比例,这可以通过调整参数 “default_statistics_target” 来实现。这个参数可在session级别设置,比如在分析不同的表时设置不同的值。

示例:
test01表数据量小,可以设置 “default_statistics_target” 为500,增大随机抽样的比例。test02表数据量大,可以设置 “default_statistics_target” 为50,减小随机抽样比例:

set default_statistics_target to 500;

analyze test01;

set default_statistics_target to 50;

analyze test02;

同样可以设置表中指定列的target值:

alter table test02 alter column addr set statistics 200;
扩展

介绍几个常用的重要的系统表和系统视图

1. pg_stat_database
数据库级的统计信息可以通过此视图查看:

postgres=# \d pg_stat_database
                        View "pg_catalog.pg_stat_database"
        Column         |           Type           | Collation | Nullable | Default 
-----------------------+--------------------------+-----------+----------+---------
 datid                 | oid                      |           |          | 
 datname               | name                     |           |          | 
 numbackends           | integer                  |           |          | 
 xact_commit           | bigint                   |           |          | 
 xact_rollback         | bigint                   |           |          | 
 blks_read             | bigint                   |           |          | 
 blks_hit              | bigint                   |           |          | 
 tup_returned          | bigint                   |           |          | 
 tup_fetched           | bigint                   |           |          | 
 tup_inserted          | bigint                   |           |          | 
 tup_updated           | bigint                   |           |          | 
 tup_deleted           | bigint                   |           |          | 
 conflicts             | bigint                   |           |          | 
 temp_files            | bigint                   |           |          | 
 temp_bytes            | bigint                   |           |          | 
 deadlocks             | bigint                   |           |          | 
 checksum_failures     | bigint                   |           |          | 
 checksum_last_failure | timestamp with time zone |           |          | 
 blk_read_time         | double precision         |           |          | 
 blk_write_time        | double precision         |           |          | 
 stats_reset           | timestamp with time zone |           |          |

特殊字段说明:

numbackends
当前有多少个并发连接,理论上控制在CPU核数的1.5倍可以获得更好的性能。

blks_read,blks_hit
两个字段分别表示读取磁盘块的次数与这些块的缓存命中数。

xact_commit,xact_rollback
提交和回滚的事务数。

deadlocks
从上次执行 pg_stat_reset 以来的死锁数量。

通过下面的查询可以计算缓存命中率:

postgres=# SELECT blks_hit::float/(blks_read + blks_hit) as cache_hit_ratio from pg_stat_database WHERE datname = current_database();
  cache_hit_ratio   
--------------------
 0.9977622095660501
(1 row)

缓存命中率是衡量 I/O 性能的最重要的指标,它应该非常接近1,否则应该调整 shared_buffers 的配置。如果命中率低于 99%,可以尝试调高它的值。

在 pg_stat_database 系统视图的字段中,除了 numbackends 字段和 stats_reset 字段外,其他字段的值是自从 stats_reset 字段记录的时间点执行 **pg_stat_reset() ** 命令以来的统计信息。建议使用者在进行优化和参数调整后执行 **pg_stat_reset() ** 命令,方便对比优化前后的各项性能指标,实际上决定查询计划的是系统表 pg_statistic ,它的数据是由 ANALYZE 命令来填充,所以不必担心执行 **pg_stat_reset() ** 命令影响查询计划。

2. pg_stat_user_tables
表级的统计信息常用的视图:

postgres=# \d pg_stat_user_tables 
                      View "pg_catalog.pg_stat_user_tables"
       Column        |           Type           | Collation | Nullable | Default 
---------------------+--------------------------+-----------+----------+---------
 relid               | oid                      |           |          | 
 schemaname          | name                     |           |          | 
 relname             | name                     |           |          | 
 seq_scan            | bigint                   |           |          | 
 seq_tup_read        | bigint                   |           |          | 
 idx_scan            | bigint                   |           |          | 
 idx_tup_fetch       | bigint                   |           |          | 
 n_tup_ins           | bigint                   |           |          | 
 n_tup_upd           | bigint                   |           |          | 
 n_tup_del           | bigint                   |           |          | 
 n_tup_hot_upd       | bigint                   |           |          | 
 n_live_tup          | bigint                   |           |          | 
 n_dead_tup          | bigint                   |           |          | 
 n_mod_since_analyze | bigint                   |           |          | 
 last_vacuum         | timestamp with time zone |           |          | 
 last_autovacuum     | timestamp with time zone |           |          | 
 last_analyze        | timestamp with time zone |           |          | 
 last_autoanalyze    | timestamp with time zone |           |          | 
 vacuum_count        | bigint                   |           |          | 
 autovacuum_count    | bigint                   |           |          | 
 analyze_count       | bigint                   |           |          | 
 autoanalyze_count   | bigint                   |           |          |

常用字段说明:

last_vacuum,last_analyze
最后一次在此表上手动执行 vacuum 和 analyze 的时间。

last_autovacuum,last_autoanalyze
最后一次在此表上被 autovacuum 守护进程执行 autovacuum 和 analyze 的时间。

idx_scan,idx_tup_fetch
在此表上进行索引扫描的次数以及以通过索引扫描得到的行数。

seq_scan,seq_tup_read
在此表上顺序扫描的次数以及以通过顺序扫描读取的行数。

n_tup_ins,n_tup_upd,n_tup_del
插入、更细、删除操作的行数。

n_live_tup,n_dead_tup
live tuple 与 dead tuple的估计数。

从性能角度看,最有意义的数据时与索引、顺序扫描相关的统计信息,当数据库可以使用索引获取数据行时会发生索引扫描。另一方面,当一个表必须被线性处理以确定哪些行属于一个集合时,会发生顺序扫描。因为实际的表数据存储在无序的堆中,读取行是一项耗时的操作,顺序扫描对于大表来说成本很高,因此应该调整索引的定义,以便于数据库尽可能少的执行顺序扫描。

索引扫描和整个数据库的所有扫描的比例可以通过以下SQL计算:

SELECT sum(idx_scan)/(sum(idx_scan) + sum(seq_scan)) as idx_scan_retio from pg_stat_all_tables WHERE schemaname='postgres';

SELECT relname,idx_scan::float/(idx_scan + seq_scan+1) as idx_scan_retio FROM  pg_stat_all_tables WHERE schemaname='postgres' ORDER BY idx_scan_retio ASC;

索引的使用率应该尽可能接近1,如果比较低应该调整索引。

3. pg_stat_statements

语句级的统计信息。

开启 pg_stat_statements 需要在 postgresql.conf 参数文件中配置,如下所示:

shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all

然后登陆数据库执行 CREATE EXTENSION 启用它,如下:

CREATE EXTENSION pg_stat_statements;