1、PGSQL执行计划的查看方式
PGSQL中查看执行计划主要使用EXPLAIN命令
EXPLAIN [(option [,…])]statement
EXPLAIN[ANALYEZ][VERBOSE]statement
命令的可选项“options”为:
ANALYZE [boolean]
VERBOSE [boolean]
COST [boolean]
BUFFERS [boolean]
TIMING [boolean]
FORMAT { TEXT | XML | JOSN | YAML }
ANALYZE:选项通过实际执行的SQL来获得相应的执行计划。因为它真正被执行,所以可以看到执行计划每一步花掉了多少时间,以及它实际返回的行数目。
注意1:加上analyze选项后,会真正执行实际的SQL,如果SQL语句是一个插入、删除、更新或create table as语句,这些语句会修改数据库数据。为了不影响实际的数据,可以把EXPLAIN ANALYZE放到一个事务中,执行完后回滚事务,如下:
begin;
explain analyze …;
rollback;
VERBOSE(默认false):选项用于显示计划的附加信息。这些附加信息有:计划树中每个节点输出的各个列,如果触发器被触发,还会输出触发器的名称。
COSTS(默认true):选项显示每个计划节点的启动成本和总成本,以及估计行数和每行宽度。
TIMING(默认true):analyze出现时可选。显示每个节点的启动时间和总时间花费。
BUFFERS(默认false):选项显示关于缓冲区使用的信息。该参数只能与anlyze参数一起使用。显示的缓冲区信息包括共享块、本地块、和临时块读和写的块数。共享块、本地块、和临时块分别包含表和索引、临时表和临时索引,以及在排序和物化计划中使用的磁盘块。上层节点显示出来的块数包括其所有子节点使用的块数。
下面为执行计划示例:
explain (analyze,buffers,verbose) select * from t1 where id=2;
testdb=> explain (analyze,buffers,verbose) select * from t1 where id=2;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Index Scan using idx_t1_id on test.t1 (cost=0.14..8.16 rows=1 width=524) (actual time=0.020..0.021 rows=1 loops=1)
Output: id, t, name
Index Cond: (t1.id = 2)
Buffers: shared hit=2
Planning Time: 0.031 ms
Execution Time: 0.031 ms
(6 rows)
结果中“Index Scan using”表示索引扫描表“idx_t1_id”;
后面的内容“(cost=0.14…8.16 rows=1 width=524)”可以分为三部分:
1)“cost=0.14…8.16”:“cost=”后面两个数字,中间是由“…”分割,第一个数字“0.14”表示启动的成本(注意这里不是时间),也就是说返回第一行pgsql需要消耗多少cost值;第二个数字表示返回所有的数据的成本。注意:每个节点中的COST都包含该节点之前节点的COST并且都为预估值。
2)rows=1:表示会返回1行。
3)width=524:表示每行平均宽度为524byte(int=4byte,character varying=2byte)。
actual部分为真实的消耗,也可以分为三个部分:
1)“time=0.020…0.021”:“0.020”为返回第一条数据花费的真实时间,“0.021”为返回所有数据花费的时间。
2)“rows=1” :真实的返回行数
3)“loops=1”:该步骤循环的次数
buffers:缓冲命中数
output: 输出的字段名
planning time: 生成执行计划时间
execution time:执行执行计划时间
2、PGSQL执行计划的阅读
阅读顺序:
嵌套层次最深的,最先执行同样嵌套深度的,从上到下,先予执行每一步的cost包括上一步。
以下面的执行计划为例:
testdb=> explain (analyze,buffers,verbose) select * from t1,t2 where t1.id=t2.id;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
1 Hash Join (cost=1.07..13.02 rows=3 width=532) (actual time=0.050..0.052 rows=3 loops=1)
Output: t1.id, t1.t, t1.name, t2.id, t2.salary
Hash Cond: (t1.id = t2.id)
Buffers: shared hit=5
2 -> Seq Scan on test.t1 (cost=0.00..11.40 rows=140 width=524) (actual time=0.007..0.007 rows=3 loops=1)
Output: t1.id, t1.t, t1.name
Buffers: shared hit=1
3 -> Hash (cost=1.03..1.03 rows=3 width=8) (actual time=0.019..0.020 rows=3 loops=1)
Output: t2.id, t2.salary
Buckets: 1024 Batches: 1 Memory Usage: 9kB
Buffers: shared hit=1
4 -> Seq Scan on test.t2 (cost=0.00..1.03 rows=3 width=8) (actual time=0.010..0.011 rows=3 loops=1)
Output: t2.id, t2.salary
Buffers: shared hit=1
Planning Time: 0.362 ms
Execution Time: 0.078 ms
(16 rows)
PG数据库的执行计划没有步骤编号,为了方便讲解这里我为每个步骤从上到下依次加上了编号。
我们现在来解读一下这个执行计划,先从上往下看,可以看到编号2和编号3这两个步骤嵌套深度是相同的。按照前面的阅读顺序规则,这里应该编号2
步骤先执行,然后再执行编号3步骤,再往下到了嵌套最深的编号4步骤,该步骤执行后最后执行编号1步骤。
最终该执行计划的执行顺序就是2->3->4->1。
知道了执行顺序还不够还需要知道每个步骤具体是在干什么,按照执行顺序来看,先看编号2步骤,'Seq Scan on test.t1’该句的意思是对test用户下的t1表进行全表扫描,后面的COST和ACTUAL部分前面已经解释过这里就不重复解释了。'Output: t1.id, t1.t, t1.name, t2.id, t2.salary’部分显示的是返回的表中哪些列。然后执行了编号3步骤,这里进行了HAHS运算把编号2步骤返回的结果生成了HASH表。然后执行编号4步骤,对t2表进行全表扫描。最后执行编号1步骤以t1表作为驱动表与T2表做HASH连接最终返回3行记录。
Explaining → 执行计划运算类型
Seq Scan: 扫描表。
Index Scan: 索引扫描。
Bitmap Index Scan:索引扫描。
Bitmap Heap Scan: 索引扫描。
Subquery Scan: 子查询。
Tid Scan:ctid = 以CTID为查询条件。
Function Scan: 函数扫描。
Nested Loop: 循环连接。
Merge Join: 排序合并连接。
Hash Join: 哈希连接。
Sort: 排序,ORDER BY操作。
Hash: 哈希运算。
Result: 函数扫描,和具体的表无关。
Unique: DISTINCT,UNION操作。
Limit: LIMIT,OFFSET操作。有
Aggregate: count, sum,avg, stddev集约函数。
Group: GROUP BY分组操作。
Append: UNION操作。
Materialize: 子查询。
SetOp: INTERCECT,EXCEPT