Postgresql执行计划浅析与案例
一、前言
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执行计划浅析与案例的更多相关文章
- SQL SERVER 2014 下IF EXITS 居然引起执行计划变更的案例分享
这个问题是在SQL SERVER 2005 升级到SQL SERVER 2014的测试过程中一同事发现的.我觉得有点意思,遂稍微修改一下脚本展示出来,本来想构造这样的一个案例来演示,但是畏惧麻烦,遂直 ...
- SQL Server实际执行计划COST"欺骗"案例
有个系统,昨天Support人员发布了相关升级脚本后,今天发现系统中有个功能不能正常使用了,直接报超时了(Timeout expired)的错误.定位到相关相关存储过程后,然后在优化分析的过程中,又遇 ...
- PostgreSQL 执行计划
简介 PostgreSQL是“世界上最先进的开源关系型数据库”.因为出现较晚,所以客户人群基数较MySQL少,但是发展势头很猛,最大优势是完全开源. MySQL是“世界上最流行的开源关系型数据库”.当 ...
- PostgreSQL执行计划:Bitmap scan VS index only scan
之前了解过postgresql的Bitmap scan,只是粗略地了解到是通过标记数据页面来实现数据检索的,执行计划中的的Bitmap scan一些细节并不十分清楚.这里借助一个执行计划来分析bitm ...
- postgreSQL执行计划
" class="wiz-editor-body wiz-readonly" contenteditable="false"> explain命 ...
- PostgreSQL执行计划的解析
一个顺序磁盘页面操作的cost值由系统参数seq_page_cost (floating point)参数指定的,由于这个参数默认为1.0,所以我们可以认为一次顺序磁盘页面操作的cost值为1.下面o ...
- 浅析SQL SERVER执行计划中的各类怪相
在查看执行计划或调优过程中,执行计划里面有些现象总会让人有些疑惑不解: 1:为什么同一条SQL语句有时候会走索引查找,有时候SQL脚本又不走索引查找,反而走全表扫描? 2:同一条SQL语句,查询条件的 ...
- PostgreSQL 与 Oracle 访问分区表执行计划差异
熟悉Oracle 的DBA都知道,Oracle 访问分区表时,对于没有提供分区条件的,也就是在无法使用分区剪枝情况下,优化器会根据全局的统计信息制定执行计划,该执行计划针对所有分区适用.在分析利弊之前 ...
- 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这四者的区别 ...
- SQL SERVER 2012 执行计划走嵌套循环导致性能问题的案例
开发人员遇到一个及其诡异的的SQL性能问题,这段完整SQL语句如下所示: declare @UserId INT declare @PSANo VAR ...
随机推荐
- Leanote蚂蚁笔记-私人云笔记服务器搭建
title: Leanote蚂蚁笔记-私人云笔记服务器搭建 date: 2020-02-22 21:53:24 categories: - [IT,技术] - [IT,软件,程序] - [IT,软件, ...
- 记一个难以发现的 UB
观察以下代码: vector<int> X, Y, A, val; inline int ls(int p) { return p << 1; } inline int rs( ...
- python进阶之路3之数据类型
内容概要 pycharm下载与使用 python语法之注释 python语法之变量与常量 python基本数据类型(先大致了解有哪些) pycharm下载与使用 1.该软件分为收费版和免费版 免费版本 ...
- Echarts自适应屏幕,无需刷新网页,可根据屏幕大小完美展现,内有详细代码注释,我可真是个小机灵~~O(∩_∩)O哈哈~
Echarts自适应屏幕,无需刷新网页,可根据屏幕大小完美展现 效果如图 随意拖拉,无惧检验 ~ ~ ~ ~ 下面上代码 里边有详细解释 <template> <div class= ...
- 动力节点——day03
接收键盘的输入:1.创建一个键盘扫描器对象 java.util.Scanner s=new Scanner(System.in); 2.接收用户输入s.nextInt(); 静态变量在类加载的时候就分 ...
- 如何使用 JuiceFS 创建 WebDAV 共享
WebDAV 是一种基于 HTTP 的文件共享协议,最初被设计用于多用户文档协作编辑的场景,也被广泛应用在基于互联网的文件存储.数据同步等网盘类应用场景. 手机端和 PC 端有大量的应用内置了对 We ...
- 创建型模式 - 单例模式Singleton
单例模式的定义与特点 创建型模式: 单例模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式. 例如,Windows中只能打开一个任务管理器,这样可以避免因打开多个任务 ...
- Ubuntu下安装cURL库用于libcurl开发
http://archive.ubuntu.com/ubuntu/pool/main/c/curl/curl_7.22.0.orig.tar.gz 不要问我为什么,根据cURL主页的下载向导给的就是这 ...
- 日志添加request-id
package com.xf.config; import java.util.Date; import javax.servlet.http.HttpServletRequest; import j ...
- Seleniumweb自动化测试01
1.selenium简介 selenium是python的一个web第三方包,主要使用来做web自动化测试的 环境要求: ①.要有谷歌浏览器 ②.下载谷歌浏览器的驱动chromedriver(驱动我们 ...