一、前言

PostgreSQL为每个收到查询产生一个查询计划。 选择正确的计划来匹配查询结构和数据的属性对于好的性能来说绝对是最关键的,因此系统包含了一个复杂的规划器来尝试选择好的计划。 你可以使用EXPLAIN命令察看规划器为任何查询生成的查询计划。

二、执行计划命令

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

这里 option可以是:
ANALYZE [ boolean ]
VERBOSE [ boolean ]
COSTS [ boolean ]
BUFFERS [ boolean ]
TIMING [ boolean ]
SUMMARY [ boolean ]
FORMAT { TEXT | XML | JSON | YAML }

ANALYZE,执行命令并且显示实际的运行时间和其他统计信息。这个参数默认被设置为FALSE。
VERBOSE,显示关于计划的额外信息。特别是:计划树中每个结点的输出列列表、模式限定的表和函数名、总是把表达式中的变量标上它们的范围表别名,以及总是打印统计信息被显示的每个触发器的名称。这个参数默认被设置为FALSE。
COSTS,包括每一个计划结点的估计启动和总代价,以及估计的行数和每行的宽度。这个参数默认被设置为TRUE。
BUFFERS,包括缓冲区使用的信息。特别是:共享块命中、读取、标记为脏和写入的次数、本地块命中、读取、标记为脏和写入的次数、以及临时块读取和写入的次数。只有当ANALYZE也被启用时,这个参数才能使用。它的默认被设置为FALSE。
TIMING,在输出中包括实际启动时间以及在每个结点中花掉的时间。只有当ANALYZE也被启用时,这个参数才能使用。它的默认被设置为TRUE。
SUMMARY,在查询计划之后包含摘要信息(例如,总计的时间信息)。当使用ANALYZE 时默认包含摘要信息,但默认情况下不包含摘要信息,但可以使用此选项启用摘要信息。 使用EXPLAIN EXECUTE中的计划时间包括从缓存中获取计划所需的时间 以及重新计划所需的时间(如有必要)。
FORMAT,指定输出格式,可以是 TEXT、XML、JSON 或者 YAML。非文本输出包含和文本输出格式相同的信息,但是更容易被程序解析。这个参数默认被设置为TEXT。
statement,你想查看其执行计划的任何SELECT、INSERT、UPDATE、DELETE、VALUES、EXECUTE、DECLARE、CREATE TABLE AS或者CREATE MATERIALIZED VIEW AS语句。

三、执行计划解读

db_test=# explain (analyze,verbose,buffers) select * from db_test.t_test;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Seq Scan on db_test.t_test(cost=0.00..22.32 rows=1032 width=56) (actual time=0.060..1.167 rows=1032 loops=1)
Output: c_bh, n_dm, c_ah
Buffers: shared read=12
Planning Time: 0.283 ms
Execution Time: 1.730 ms

解读

cost=0.00..22.32,0.00代表启动成本,22.32代表返回所有数据的成本。
rows=1032:表示返回多少行。
width=56,表示每行平均宽度。
actual time=0.060..1.167,实际花费的时间。
loops=1,循环的次数
Output,输出的字段名
Buffers,缓冲命中数
shared read,代表数据来自disk(磁盘)而并非cache(缓存),当再次执行sql,会发现变成shared hit,说明数据已经在cache中
Planning Time,生成执行计划的时间
Execution Time,执行执行计划的时间

四、常见扫描方式

PostgreSQL中数据扫描方式很多,常见有如下几种

1、seq scan

顺序扫描

db_test=# explain (analyze,verbose,buffers) select * from db_test.t_ms_aj;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Seq Scan on db_test.t_ms_aj (cost=0.00..22.32 rows=1032 width=56) (actual time=0.060..1.167 rows=1032 loops=1)
Output: c_bh, n_dm, c_ah
Buffers: shared hit=12
Planning Time: 0.283 ms
Execution Time: 1.730 ms

2、Index Only Scan

按索引顺序扫描,通过VM减少回表,绝大数情况下不需要回表。

db_test=# explain (analyze,verbose,buffers) select c_bh from db_test.t_ms_aj where c_bh='db22f5a4f828d0f4eaa0b70679a4d637';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Index Only Scan using t_ms_aj_pkey on db_test.t_ms_aj (cost=0.28..8.29 rows=1 width=33) (actual time=0.079..0.081 rows=1 loops=1)
Output: c_bh
Index Cond: (t_ms_aj.c_bh = 'db22f5a4f828d0f4eaa0b70679a4d637'::bpchar)
Heap Fetches: 1
Buffers: shared hit=3
Planning Time: 0.139 ms
Execution Time: 0.166 ms

3、Index Scan

按索引顺序扫描,并回表。

db_test=# explain (analyze,buffers) select * from db_test.t_ms_aj where c_bh='db22f5a4f828d0f4eaa0b70679a4d637';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
Index Scan using t_ms_aj_pkey on t_ms_aj (cost=0.28..8.29 rows=1 width=56) (actual time=0.890..0.894 rows=1 loops=1)
Index Cond: (c_bh = 'db22f5a4f828d0f4eaa0b70679a4d637'::bpchar)
Buffers: shared hit=3
Planning Time: 0.376 ms
Execution Time: 1.136 ms

4、Bitmap Index Scan+Bitmap Heap Scan

按索引取得的BLOCKID排序,然后根据BLOCKID顺序回表扫描,然后再根据条件过滤掉不符合条件的记录。这种扫描方法,主要解决了离散数据(索引字段的逻辑顺序与记录的实际存储顺序非常离散的情况),需要大量离散回表扫描的情况

db_test=# explain (analyze,verbose,buffers) select * from db_test.t_ms_aj_bak where n_dm=12;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on db_test.t_ms_aj_bak (cost=100.85..1303.26 rows=5233 width=56) (actual time=1.477..107.896 rows=5204 loops=1)
Output: c_bh, n_dm, c_ah
Recheck Cond: (t_ms_aj_bak.n_dm = 12)
Heap Blocks: exact=1125
Buffers: shared hit=1126 read=15
-> Bitmap Index Scan on i_ms_aj_bak_n_dm (cost=0.00..99.54 rows=5233 width=0) (actual time=1.260..1.260 rows=5204 loops=1)
Index Cond: (t_ms_aj_bak.n_dm = 12)
Buffers: shared hit=1 read=15
Planning Time: 0.114 ms
Execution Time: 109.361 ms

5、Hash join

哈希JOIN

db_test=# explain (analyze,verbose,buffers) select aj.c_bh from db_test.t_ms_aj aj join db_test.t_ms_dsr dsr on dsr.c_bh_aj=aj.c_bh;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=35.22..5378.59 rows=2307 width=33) (actual time=3.121..1254.234 rows=2074 loops=1)
Output: aj.c_bh
Inner Unique: true
Hash Cond: (dsr.c_bh_aj = aj.c_bh)
Buffers: shared hit=2828
-> Seq Scan on db_test.t_ms_dsr dsr (cost=0.00..4817.86 rows=200186 width=33) (actual time=0.013..598.660 rows=200186 loops=1)
Output: dsr.c_bh, dsr.c_bh_aj, dsr.c_name
Buffers: shared hit=2816
-> Hash (cost=22.32..22.32 rows=1032 width=33) (actual time=3.089..3.089 rows=1032 loops=1)
Output: aj.c_bh
Buckets: 2048 Batches: 1 Memory Usage: 82kB
Buffers: shared hit=12
-> Seq Scan on db_test.t_ms_aj aj (cost=0.00..22.32 rows=1032 width=33) (actual time=0.010..1.860 rows=1032 loops=1)
Output: aj.c_bh
Buffers: shared hit=12
Planning Time: 0.396 ms
Execution Time: 1257.348 ms

6、Nested Loop

嵌套循环。其中一个表扫描一次,另一个表则循环多次。

db_test=# explain analyze select aj.c_bh from db_test.t_ms_aj aj join db_test.t_ms_dsr dsr on dsr.c_bh_aj=aj.c_bh where aj.n_dm=20;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=8.87..263.50 rows=45 width=33) (actual time=0.058..0.405 rows=37 loops=1)
-> Bitmap Heap Scan on t_ms_aj aj (cost=4.43..17.09 rows=20 width=33) (actual time=0.028..0.067 rows=20 loops=1)
Recheck Cond: (n_dm = 20)
Heap Blocks: exact=10
-> Bitmap Index Scan on i_ms_aj_n_dm (cost=0.00..4.43 rows=20 width=0) (actual time=0.018..0.019 rows=20 loops=1)
Index Cond: (n_dm = 20)
-> Bitmap Heap Scan on t_ms_dsr dsr (cost=4.44..12.30 rows=2 width=33) (actual time=0.014..0.015 rows=2 loops=20)
Recheck Cond: (c_bh_aj = aj.c_bh)
Heap Blocks: exact=20
-> Bitmap Index Scan on i_ms_dsr_c_bh (cost=0.00..4.43 rows=2 width=0) (actual time=0.011..0.011 rows=2 loops=20)
Index Cond: (c_bh_aj = aj.c_bh)
Planning Time: 0.409 ms
Execution Time: 0.555 ms

7、Merge Join

需要两个JOIN的表的KEY都是先排好顺序的,如果有索引没有排序过程。Merge Join两个表都只扫描一次。

db_test=# explain analyze select aj.c_bh from db_test.t_ms_aj aj join db_test.t_ms_dsr dsr on dsr.c_bh=aj.c_ah;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=14985.80..15689.01 rows=1032 width=33) (actual time=944.856..951.963 rows=0 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Merge Join (cost=13985.80..14585.81 rows=607 width=33) (actual time=851.573..851.573 rows=0 loops=2)
Merge Cond: (dsr.c_bh = (aj.c_ah)::bpchar)
-> Sort (cost=13911.82..14206.21 rows=117756 width=33) (actual time=747.508..792.472 rows=100093 loops=2)
Sort Key: dsr.c_bh
Sort Method: quicksort Memory: 11282kB
Worker 0: Sort Method: quicksort Memory: 10503kB
-> Parallel Seq Scan on t_ms_dsr dsr (cost=0.00..3993.56 rows=117756 width=33) (actual time=0.035..115.401 rows=100093 loops=2)
-> Sort (cost=73.98..76.56 rows=1032 width=52) (actual time=2.963..3.154 rows=338 loops=2)
Sort Key: aj.c_ah USING <
Sort Method: quicksort Memory: 194kB
Worker 0: Sort Method: quicksort Memory: 194kB
-> Seq Scan on t_ms_aj aj (cost=0.00..22.32 rows=1032 width=52) (actual time=0.082..0.545 rows=1032 loops=2)
Planning Time: 0.481 ms
Execution Time: 952.152 ms

五、示例

1、我们先看sql,根据现场反馈修改其他c_dbbh后,sql执行较快,唯独这一个c_dbbh需要一分钟才会出结果

SELECT COUNT(1)
FROM db_test.t_zh_axx aj
LEFT JOIN (SELECT n_ccsl,c_ajbh,c_zblx,c_laay FROM db_test.t_zh_zjxx A
WHERE
c_bh = (SELECT c_bh FROM db_test.t_zh_zjxx b WHERE b.c_ajbh = A.c_ajbh AND b.c_zblx = '0050002' ORDER BY dt_cjsj DESC NULLS LAST LIMIT 1)
) zb ON zb.c_ajbh = aj.c_ajbh
WHERE
c_dbbh = '0191H4325678D8172F58EE383720D0A9'
AND zb.c_zblx = '0050002'
AND aj.c_zy = '1628'
--涉及表的数据量
--db_test.t_zh_axx 1200万+
--db_test.t_zh_zjxx 1900万+

2、先看执行计划为什么慢,在进行sql优化。

Aggregate (cost=3214.11..3214.12 rows=1 width=8) (actual time=61097.734..61097.734 rows=1 loops=1)
-> Nested Loop (cost=743.92..3214.11 rows=1 width=0) (actual time=8.702..61097.110 rows=1461 loops=1)
-> Bitmap Heap Scan on t_zh_axx aj (cost=743.36..763.39 rows=5 width=17) (actual time=8.585..11.327 rows=1461 loops=1)
Recheck Cond:((c_dbbh = '0191H4325678D8172F58EE383720D0A9'::bpchar) AND ((c_zy)::text = '1801'::text))
Heap Blocks:exact=720
-> BitmapAnd (cost=743.36..743.36 rows=5 width=0)(actual time=8.479..8.479 rows=0 loops=1)
-> Bitmap Index Scan on i_t_zh_axx_c_dbbh_c_cbfy (cost=0.00..70.63 rows=1343 width=0)(actual time=0.766..0.766 rows=0 loops=1)
Index Cond:(c_dbbh = '0191H4325678D8172F58EE383720D0A9'::bpchar)
-> Bitmap Index Scan on i_t_zh_axx_zblx_c_zy (cost=0.00..672.47 rows=36272 width=0)(actual time=7.644..7.644 rows=0 loops=1)
Index Cond:((c_zy)::text = '1628'::text)
-> Index Scan using i_t_zh_zjxx_c_ajbh on t_zh_zjxx a (cost=0.56..190.13 rows=1 width=16) (actual time=20.069..41.822 rows=1 loops=1461)
Index Cond: ((c_ajbh)::text = (aj.c_ajbh)::text)
Filter: (((c_zblx)::text = '0020002'::text) AND (c_bh = (SubPlan 1)))
Rows Removed by Filter: 167
SubPlan 1
-> Limit (cost=44.55..44.56 rows=1 width=41) (actual time=0.247..0.248 rows=1 loops=245603)
-> Sort (cost=44.55..44.56 rows=1 width=41) (actual time=0.247..0.247 rows=1 loops=245603)
Sort Key: b.dt_cjsj DESC NULLS LAST
Sort Method: top-N heapsort Memory: 25kB
-> Index Scan using i_t_zh_zjxx_c_ajbh on t_zh_zjxx b (cost=0.56..54.82 rows=1 width=41) (actual time=0.017..0.071 rows=38 loops=245603)
Index Cond: ((c_ajbh)::text = (a.c_ajbh)::text)
Filter: ((c_zblx)::text = '0020002'::text)
Rows Removed by Filter: 114
Planning Time: 1.267 ms
Execution Time: 61097.876 ms

分析

仔细观察执行计划后发现,占用时间最多的在第2行Nested Loop(actual time=8.702…61097.110),嵌套循环占用了一分钟,然后在16 -> 20行看到(loops=245603),循环了24.5万次。
上篇文章可以了解到嵌套循环:其中一个表扫描一次,另一个表则循环多次。这里基本可以确定问题了,找开发确认发现是确实是数据问题,原因是重复上报导致。

3、改写sql

--用exists改写
explain analyze
select count(*) from db_test.t_zh_axx aj
where exists (select 1 from db_test.t_zh_zjxx zbajxx where zbajxx.c_ajbh=aj.c_ajbh and c_zblx = '0020002')
and aj.c_dbbh = '8B7D8C93864E0D0C3E3259C49ED65471'
AND aj.c_zy = '1801'

--执行计划都走索引
--值得关注的是join的方式是Nested Loop Semi Join,多了个Semi。Semi Join支持支持hash, merge, nestloop几种JOIN方法
--semi Join的操作在EXISTS中有一个返回TRUE的操作即可,所以在有索引的情况下很大概率下并不需要全表扫描
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=5560.83..5560.84 rows=1 width=8) (actual time=38.158..38.158 rows=1 loops=1)
-> Nested Loop Semi Join, (cost=1.12..5560.82 rows=6 width=0) (actual time=0.072..37.912 rows=1356 loops=1)
-> Index Scan using t_zh_axx_c_dbbh_idx on t_zh_axx aj (cost=0.56..5210.98 rows=6 width=17) (actual time=0.042..1.315 rows=1461 loops=1)
Index Cond: (c_dbbh = '8B7D8C93864E0D0C3E3259C49ED65471'::bpchar)
Filter: ((c_zy)::text = '1801'::text)
-> Index Scan using i_t_zh_zjxx_c_ajbh on t_zh_zjxx zbajxx (cost=0.56..58.50 rows=1 width=16) (actual time=0.024..0.024 rows=1 loops=1461)
Index Cond: ((c_ajbh)::text = (aj.c_ajbh)::text)
Filter: ((c_zblx)::text = '0020002'::text)
Planning Time: 0.852 ms
Execution Time: 38.255 ms

Postgresql执行计划浅析与案例的更多相关文章

  1. SQL SERVER 2014 下IF EXITS 居然引起执行计划变更的案例分享

    这个问题是在SQL SERVER 2005 升级到SQL SERVER 2014的测试过程中一同事发现的.我觉得有点意思,遂稍微修改一下脚本展示出来,本来想构造这样的一个案例来演示,但是畏惧麻烦,遂直 ...

  2. SQL Server实际执行计划COST"欺骗"案例

    有个系统,昨天Support人员发布了相关升级脚本后,今天发现系统中有个功能不能正常使用了,直接报超时了(Timeout expired)的错误.定位到相关相关存储过程后,然后在优化分析的过程中,又遇 ...

  3. PostgreSQL 执行计划

    简介 PostgreSQL是“世界上最先进的开源关系型数据库”.因为出现较晚,所以客户人群基数较MySQL少,但是发展势头很猛,最大优势是完全开源. MySQL是“世界上最流行的开源关系型数据库”.当 ...

  4. PostgreSQL执行计划:Bitmap scan VS index only scan

    之前了解过postgresql的Bitmap scan,只是粗略地了解到是通过标记数据页面来实现数据检索的,执行计划中的的Bitmap scan一些细节并不十分清楚.这里借助一个执行计划来分析bitm ...

  5. postgreSQL执行计划

    " class="wiz-editor-body wiz-readonly" contenteditable="false"> explain命 ...

  6. PostgreSQL执行计划的解析

    一个顺序磁盘页面操作的cost值由系统参数seq_page_cost (floating point)参数指定的,由于这个参数默认为1.0,所以我们可以认为一次顺序磁盘页面操作的cost值为1.下面o ...

  7. 浅析SQL SERVER执行计划中的各类怪相

    在查看执行计划或调优过程中,执行计划里面有些现象总会让人有些疑惑不解: 1:为什么同一条SQL语句有时候会走索引查找,有时候SQL脚本又不走索引查找,反而走全表扫描? 2:同一条SQL语句,查询条件的 ...

  8. PostgreSQL 与 Oracle 访问分区表执行计划差异

    熟悉Oracle 的DBA都知道,Oracle 访问分区表时,对于没有提供分区条件的,也就是在无法使用分区剪枝情况下,优化器会根据全局的统计信息制定执行计划,该执行计划针对所有分区适用.在分析利弊之前 ...

  9. MySQL 执行计划中Extra(Using where,Using index,Using index condition,Using index,Using where)的浅析

      关于如何理解MySQL执行计划中Extra列的Using where.Using Index.Using index condition,Using index,Using where这四者的区别 ...

  10. SQL SERVER 2012 执行计划走嵌套循环导致性能问题的案例

    开发人员遇到一个及其诡异的的SQL性能问题,这段完整SQL语句如下所示: declare @UserId             INT declare @PSANo              VAR ...

随机推荐

  1. PostgreSQL和MySQL的优劣对比

    在开发项目的过程中,难免要面对选择数据库的情况.总结此文章是因为在之前公司里使用的都是MYSQL 数据库,而在现在公司里,新项目中使用的是 PostgreSQL 数据库,在使用过程中,经常需要查找两种 ...

  2. 使用 SSH 连接 Git 服务器

    关于 SSH SSH (Secure Shell) 是一种安全的远程登录协议,可以让你通过安全的加密连接进行远程登录.目前,Mac.Windows 10.Linux 系统均有内置 OpenSSH 客户 ...

  3. vue 点击按钮添加一行dom节点

    如图,最近项目需求,点击添加一行dom节点,包含下拉框和input输入框 ,下面展示一下代码 <ul class="sales-menuItem-ul"> <li ...

  4. Vulnhub之Credit_Card_Scammers靶场渗透

    前言 一次"夺旗"练习,涵盖了许多不同的技巧. 背后的故事:骗子正在利用人们,各种假冒购物网站已经建立起来,但人们发现他们的订单从未到达.我们发现了一个诈骗网站,我们认为该网站正在 ...

  5. 启动springboot项目报错Unable to start embedded Tomcat

    1.问题描述 最近在学习springcloud的时候,在父工程下新建一个model后,引入dashboard相关依赖后启动报错 2.产生原因 产生原因有可能就是pom.xml中下载的jar包版本冲突 ...

  6. 使用IDEA创建一个maven的web项目并部署到tomcat上

    目录 1.创建一个maven项目 2.为项目添加配置文件 3.创建一些类和jsp页面 4.将项目部署到tomcat 1.创建一个maven项目 打开IDEA,File--New--Project 选择 ...

  7. 使用 Helm 安装 MQTT 服务器-EMQX

    EMQX ️ Info: 使用 EMQX 通过 Helm3 在 Kubernetes 上部署 EMQX 4.0 集群 | EMQ emqx/deploy/charts/emqx at main-v4. ...

  8. [论文总结] Genecology and Adaptation of Forest Trees 林木的基因生态学与适应性

    文章目录 介绍 进化的力量 基因学方法 种源试验 短期基因检测实验 表型与遗传估计 差异化 基因学趋势 预测对气候变化的反应 介绍 基因生态学是研究种内遗传变异与环境条件的关系.它揭示了种群适应环境的 ...

  9. BalticOI 2004 Sequence 题解

    题目链接在这里~ 对于序列\(\{a\}\),把每一个\(a_i\)减去一个\(i\),得到\(\{a'\}\)序列\(\{b\}\)同理. 因为\(b_1<b_2<...<b_n\ ...

  10. Angularjs的重要概念

    AngularJS的重要概念 MVC模式 AngularJS最早按照MVC模式设计,在这种设计模式下,AngularJS组件可以分为: M: Model,即模型,是应用程序中用于处理应用程序数据逻辑的 ...