PostgreSQL 执行计划
简介
PostgreSQL是“世界上最先进的开源关系型数据库”。因为出现较晚,所以客户人群基数较MySQL少,但是发展势头很猛,最大优势是完全开源。
MySQL是“世界上最流行的开源关系型数据库”。当前客户基数大,随着被Oracle收购,开源程度减小,尤其是近期单独拉了免费的MariaDB分支,更表明MySQL有闭源的倾向;
至于两者孰优孰劣,不是本文要讨论的重点,在一般的使用中,没什么大的差别,下面我们只讨论PG中执行计划。
执行计划
pg在查询规划路径过程中,查询请求的不同执行方案是通过建立不同的路径来表达的,在生成较多符合条件的路径之后,要从中选择出代价最小的路径,把它转化为一个执行计划,传递给执行器执行。那么如何生成最小代价的计划呢?基于统计信息估计计划中各个节点的成本,其中与之相关的参数如下所示:
计算代价:
# 估算代价:
total_cost = seq_page_cost * relpages + cpu_tuple_cost * reltuples
# 有时我们不想用系统默认的执行计划,这时可以通过禁止/开启某种运算的语法来强制控制执行计划:
enable_bitmapscan = on
enable_hashagg = on
enable_hashjoin = on
enable_indexscan = on #索引扫描
enable_indexonlyscan = on #只读索引扫描
enable_material = on #物化视图
enable_mergejoin = on
enable_nestloop = on
enable_seqscan = on
enable_sort = on
enable_tidscan = on
# 按照上面扫描方式并过滤代价:
Cost = seq_page_cost * relpages + cpu_tuple_cost * reltuples + cpu_operation_cost * reltuples
每个SQL语句都会有自己的执行计划,我们可以使用explain指令获取执行计划,语法如下:
nsc=# \h explain;
Command: EXPLAIN
Description: show the execution plan of a statement
Syntax:
EXPLAIN [ ( option [, ...] ) ] statement
EXPLAIN [ ANALYZE ] [ VERBOSE ] statement where option can be one of: ANALYZE [ boolean ] -- 是否真正执行,默认false
VERBOSE [ boolean ] -- 是否显示详细信息,默认false
COSTS [ boolean ] -- 是否显示代价信息,默认true
BUFFERS [ boolean ] -- 是否显示缓存信息,默认false,前置事件是analyze
TIMING [ boolean ] -- 是否显示时间信息
FORMAT { TEXT | XML | JSON | YAML } -- 输格式,默认为text
如下图所示,cost是比较重要的指标,cost=1000.00..1205.30,执行sql代价,分为两个部分,前一部分表示启动时间(startup)是1000ms,执行到返回第一行时需要的cost值,后一部分表示总时间(total)是1205.30ms,执行整个SQL的cost。rows表示预测的行数,与实际的记录数可能有出入,数据库经常vacuum或analyze,该值越接近实际值。width表示查询结果的所有字段的总宽度为285个字节。
可以在explain后添加analyze关键字来通过执行这个SQL获得真实的执行计划和执行时间,actual time中的第一个数字表示返回第一行需要的时间,第二个数字表示执行整个sql花费的时间。loops为该节点循环次数,当loops大于1时,总成本为:actual time * loops
执行计划节点类型
在PostgreSQL的执行计划中,是自上而下阅读的,通常执行计划会有相关的索引来表示不同的计划节点,其中计划节点类型分为四类:控制节点(Control Node),扫描节点(Scan Node),物化节点(Materialization Node),连接节点(Join Node)。
控制节点:append,组织多个字表或子查询的执行节点,主要用于union操作。
扫描节点:用于扫描表等对象以获取元组
Seq Scan(全表扫描):把表的所有数据块从头到尾读一遍,筛选出符合条件的数据块;
Index Scan(索引扫描):为了加快查询速度,在索引中找到需要的数据行的物理位置,再到表数据块中把对应数据读出来,如B树,GiST,GIN,BRIN,HASH
Bitmap Index/Heap Scan(位图索引/结果扫描):把满足条件的行或块在内存中建一个位图,扫描完索引后,再根据位图列表的数据文件把对应的数据读出来,先通过Bitmap Index Scan在索引中找到符合条件的行,在内存中建立位图,之后再到表中扫描Bitmap Heap Scan。
物化节点:能够缓存执行结果到缓存中,即第一次被执行时生成的结果元组缓存,等待上层节点使用,例如,sort节点能够获取下层节点返回的所有元组并根据指定的属性排序,并将排序结果缓存,每次上层节点取元组时就从缓存中按需读取。
Materialize:对下层节点返回的元组进行缓存(如连接表时)
Sort:对下层返回的节点进行排序(如果内存超过iwork_mem参数指定大小,则节点工作空间切换到临时文件,性能急剧下降)
Group:对下层排序元组进行分组操作
Agg:执行聚集函数(sum/max/min/avg)
条件过滤,一般在where后加上过滤条件,当扫描数据行时,会找出满足过滤条件的行,条件过滤在执行计划里面显示Filter,如果条件的列上面有索引,可能会走索引,不会走过滤。
连接节点:对应于关系代数中的连接操作,可以实现多种连接方式(条件连接/左连接/右连接/全连接/自然连接)
Nestedloop Join(嵌套连接): 内表被外表驱动,外表返回的每一行都要在内表中检索找到与它匹配的行,因此整个查询返回的结果集不能太大,要把返回子集较小的表作为外表,且内表的连接字段上要有索引。 执行过程为,确定一个驱动表(outer table),另一个表为inner table,驱动表中每一行与inner table中的相应记录关联;
Hash Join(哈希连接):优化器使用两个比较的表,并利用连接属性在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行;
Merge Join(合并连接):通常hash连接的性能要比merge连接好,但如果源数据上有索引,或结果已经被排过序,这时merge连接性能会优于hash连接;
运算类型(explain)
运算类型 | 操作说明 | 是否有启动时间 |
Seq Scan | 顺序扫描表 | 无启动时间 |
Index Scan | 索引扫描 | 无启动时间 |
Bitmap Index Scan | 索引扫描 | 有启动时间 |
Bitmap Heap Scan | 索引扫描 | 有启动时间 |
Subquery Scan | 子查询 | 无启动时间 |
Tid Scan | 行号检索 | 无启动时间 |
Function Scan | 函数扫描 | 无启动时间 |
Nested Loop Join | 嵌套连接 | 无启动时间 |
Merge Join | 合并连接 | 有启动时间 |
Hash Join | 哈希连接 | 有启动时间 |
Sort | 排序(order by) | 有启动时间 |
Hash | 哈希运算 | 有启动时间 |
Result | 函数扫描,和具体的表无关 | 无启动时间 |
Unique | distinct/union | 有启动时间 |
Limit | limit/offset | 有启动时间 |
Aggregate | count, sum,avg等聚集函数 | 有启动时间 |
Group | group by | 有启动时间 |
Append | union操作 | 无启动时间 |
Materialize | 子查询 | 有启动时间 |
SetOp | intersect/except | 有启动时间 |
示例讲解
慢sql如下:
SELECT
te.event_type,
sum(tett.feat_bytes) AS traffic
FROM t_event te
LEFT JOIN t_event_traffic_total tett
ON tett.event_id = te.event_id
WHERE
((te.event_type >= 1 AND te.event_type <= 17) OR (te.event_type >= 23 AND te.event_type <= 26) OR (te.event_type >= 129 AND te.event_type <= 256))
AND te.end_time >= '2017-10-01 09:39:41+08:00'
AND te.begin_time <= '2018-01-01 09:39:41+08:00'
AND tett.stat_time >= '2017-10-01 09:39:41+08:00'
AND tett.stat_time < '2018-01-01 09:39:41+08:00'
GROUP BY te.event_type
ORDER BY total_count DESC
LIMIT 10
耗时:约4s
作用:事件表和事件流量表关联,查出一段时间内按照总流量大小排列的TOP10事件类型
记录数:
select count(1) from t_event; -- 535881条
select count(1) from t_event_traffic_total; -- 2123235条
结果:
event_type traffic
17 2.26441505638877E17
2 2.25307250128674E17
7 1.20629298837E15
26 285103860959500
1 169208970599500
13 47640495350000
6 15576058500000
3 12671721671000
15 1351423772000
11 699609230000
执行计划:
Limit (cost=5723930.01..5723930.04 rows=10 width=12) (actual time=3762.383..3762.384 rows=10 loops=1)
Output: te.event_type, (sum(tett.feat_bytes))
Buffers: shared hit=1899 read=16463, temp read=21553 written=21553
-> Sort (cost=5723930.01..5723930.51 rows=200 width=12) (actual time=3762.382..3762.382 rows=10 loops=1)
Output: te.event_type, (sum(tett.feat_bytes))
Sort Key: (sum(tett.feat_bytes))
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=1899 read=16463, temp read=21553 written=21553
-> HashAggregate (cost=5723923.69..5723925.69 rows=200 width=12) (actual time=3762.360..3762.363 rows=18 loops=1)
Output: te.event_type, sum(tett.feat_bytes)
Buffers: shared hit=1899 read=16463, temp read=21553 written=21553
-> Merge Join (cost=384982.63..4390546.88 rows=266675361 width=12) (actual time=2310.395..3119.886 rows=2031023 loops=1)
Output: te.event_type, tett.feat_bytes
Merge Cond: (te.event_id = tett.event_id)
Buffers: shared hit=1899 read=16463, temp read=21553 written=21553
-> Sort (cost=3284.60..3347.40 rows=25119 width=12) (actual time=21.509..27.978 rows=26225 loops=1)
Output: te.event_type, te.event_id
Sort Key: te.event_id
Sort Method: external merge Disk: 664kB
Buffers: shared hit=652, temp read=84 written=84
-> Append (cost=0.00..1448.84 rows=25119 width=12) (actual time=0.027..7.975 rows=26225 loops=1)
Buffers: shared hit=652
-> Seq Scan on public.t_event te (cost=0.00..0.00 rows=1 width=12) (actual time=0.001..0.001 rows=0 loops=1)
Output: te.event_type, te.event_id
Filter: ((te.end_time >= '2017-10-01 09:39:41+08'::timestamp with time zone) AND (te.begin_time <= '2018-01-01 09:39:41+08'::timestamp with time zone) AND (((te.event_type >= 1) AND (te.event_type <= 17)) OR ((te.event_type >= 23) AND (te.event_type <= 26)) OR ((te.event_type >= 129) AND (te.event_type <= 256))))
-> 扫描子表过程,省略...
-> Materialize (cost=381698.04..392314.52 rows=2123296 width=16) (actual time=2288.881..2858.256 rows=2123235 loops=1)
Output: tett.feat_bytes, tett.event_id
Buffers: shared hit=1247 read=16463, temp read=21469 written=21469
-> Sort (cost=381698.04..387006.28 rows=2123296 width=16) (actual time=2288.877..2720.994 rows=2123235 loops=1)
Output: tett.feat_bytes, tett.event_id
Sort Key: tett.event_id
Sort Method: external merge Disk: 53952kB
Buffers: shared hit=1247 read=16463, temp read=21469 written=21469
-> Append (cost=0.00..49698.20 rows=2123296 width=16) (actual time=0.026..470.610 rows=2123235 loops=1)
Buffers: shared hit=1247 read=16463
-> Seq Scan on public.t_event_traffic_total tett (cost=0.00..0.00 rows=1 width=16) (actual time=0.001..0.001 rows=0 loops=1)
Output: tett.feat_bytes, tett.event_id
Filter: ((tett.stat_time >= '2017-10-01 09:39:41+08'::timestamp with time zone) AND (tett.stat_time < '2018-01-01 09:39:41+08'::timestamp with time zone))
-> 扫描子表过程,省略...
Total runtime: 3771.346 ms
执行计划解读:
第40->30行:通过结束时间上创建的索引,顺序扫描t_event_traffic_total表,根据时间跨度三个月过滤出符合条件的数据,共2123235条记录;
第26->21行:根据时间过滤出t_event表中符合条件的记录,共26225条记录;
第30->27行:根据流量大小排序,执行sort操作;
第12->09行:两个表执行join操作,执行完记录200条;
第08->04行:对最终的200条记录按照大小排序;
第01行:执行limit取10条记录。
整个执行计划中花时间最长的是根据时间条件过滤t_event_traffic_total表,因为字表较多,记录较多,导致花费2.8s之多,所以我们优化的思路就比较简单了,直接根据actual time,花费较多的子表去查看表中是否有索引,以及记录是不是很多,有没有优化的空间,而经过排查,发现一个子表中的数据量达到1531147条。
pg_hint_plan定制执行计划
原文链接:https://blog.csdn.net/JAVA528416037/article/details/91998019
PostgreSQL 执行计划的更多相关文章
- 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 ...
- PostgreSQL 与 Oracle 访问分区表执行计划差异
熟悉Oracle 的DBA都知道,Oracle 访问分区表时,对于没有提供分区条件的,也就是在无法使用分区剪枝情况下,优化器会根据全局的统计信息制定执行计划,该执行计划针对所有分区适用.在分析利弊之前 ...
- PostgreSQL EXPLAIN执行计划学习--多表连接几种Join方式比较
转了一部分.稍后再修改. 三种多表Join的算法: 一. NESTED LOOP: 对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择.在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表 ...
- MySQL统计信息以及执行计划预估方式初探
数据库中的统计信息在不同(精确)程度上描述了表中数据的分布情况,执行计划通过统计信息获取符合查询条件的数据大小(行数),来指导执行计划的生成.在以Oracle和SQLServer为代表的商业数据库,和 ...
- Postgresql_根据执行计划优化SQL
执行计划路径选择 postgresql查询规划过程中,查询请求的不同执行方案是通过建立不同的路径来表达的,在生成许多符合条件的路径之后,要从中选择出代价最小的路径,把它转化为一个计划,传递给执行器执行 ...
- ORACLE从共享池删除指定SQL的执行计划
Oracle 11g在DBMS_SHARED_POOL包中引入了一个名为PURGE的新存储过程,用于从对象库缓存中刷新特定对象,例如游标,包,序列,触发器等.也就是说可以删除.清理特定SQL的执行计划 ...
- MSSQLSERVER执行计划详解
序言 本篇主要目的有二: 1.看懂t-sql的执行计划,明白执行计划中的一些常识. 2.能够分析执行计划,找到优化sql性能的思路或方案. 如果你对sql查询优化的理解或常识不是很深入,那么推荐几骗博 ...
随机推荐
- 关于C语言指针的讨论
C语言指针的讨论 1.指整的概念辨析 2.指针与一维数组 3.指针与二维数组 4.指针与动态数组 5.指针数组 6. 指整与函数,形参,返回值 先熟悉一下概念,使劲把他们记下了 变量定义 类型表示 含 ...
- Shell获取指定区间随机未占用的端口号
说明 最近在写Jenkins自动运维的脚本,由于是用的docker,部署的时候启动容器端口号冲突会导致部署失败,用的微服务也不在乎端口什么的,只求部署成功,所以想了很久,参考了一些文章,还有运维大哥的 ...
- 基于 DNS 动态发现方式部署 Etcd 集群
使用discovery的方式来搭建etcd集群方式有两种:etcd discovery和DNS discovery.在 「基于已有集群动态发现方式部署etcd集群」一文中讲解了etcd discove ...
- linux或者shell进入vi命令
vi的基本操作 a) 进入vi 在系统提示符号输入vi及文件名称后,就进入vi全屏幕编辑画面: $ vi file 不过有一点要特别注意,就是您进入vi之后,是处于「命令行模式(comman ...
- Linux学习笔记之Centos7 自定义systemctl服务脚本
0x00 概述 之前工作环境一直使用Centos6版本,脚本一直在使用/etc/init.d/xxx:系统升级到Cento7后,虽然之前的启动脚本也可以使用,但一直没有使用systemctl 的自定义 ...
- css元素水平垂直居中
温习一下元素水平垂直居中的几种方法 元素有具体宽度 1.absolute+负边距 .LV_center{ border: 1px solid red; position: absolute; widt ...
- String.Operation
// 字符串切割 StringField.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
- asp.net core MVC 过滤器之ExceptionFilter过滤器(一)
简介 异常过滤器,顾名思义,就是当程序发生异常时所使用的过滤器.用于在系统出现未捕获异常时的处理. 实现一个自定义异常过滤器 自定义一个异常过滤器需要实现IExceptionFilter接口 publ ...
- Python删除列表元素的3种方法
之前看教程的时候比较着急,对这些基础掌握不好,过来回顾一下 使用del语句删除 lis = [1, 2, 3, 'a', 'b'] print(lis) del lis[0] print(lis) 输 ...
- [转发] SAP EPIC 银企直连+TRM资金管理
事务代码:EPIC_PROC 电子支付集成 收款; 付款; 付款审批; 银行回单(下载,创建,修改,辩识,认领); 查询账户余额; 查询交易明细; BADI增强; VA虚拟账户客户回单自动辨识; .. ...