简介

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 执行计划的更多相关文章

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

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

  2. postgreSQL执行计划

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

  3. PostgreSQL执行计划的解析

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

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

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

  5. PostgreSQL EXPLAIN执行计划学习--多表连接几种Join方式比较

    转了一部分.稍后再修改. 三种多表Join的算法: 一. NESTED LOOP: 对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择.在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表 ...

  6. MySQL统计信息以及执行计划预估方式初探

    数据库中的统计信息在不同(精确)程度上描述了表中数据的分布情况,执行计划通过统计信息获取符合查询条件的数据大小(行数),来指导执行计划的生成.在以Oracle和SQLServer为代表的商业数据库,和 ...

  7. Postgresql_根据执行计划优化SQL

    执行计划路径选择 postgresql查询规划过程中,查询请求的不同执行方案是通过建立不同的路径来表达的,在生成许多符合条件的路径之后,要从中选择出代价最小的路径,把它转化为一个计划,传递给执行器执行 ...

  8. ORACLE从共享池删除指定SQL的执行计划

    Oracle 11g在DBMS_SHARED_POOL包中引入了一个名为PURGE的新存储过程,用于从对象库缓存中刷新特定对象,例如游标,包,序列,触发器等.也就是说可以删除.清理特定SQL的执行计划 ...

  9. MSSQLSERVER执行计划详解

    序言 本篇主要目的有二: 1.看懂t-sql的执行计划,明白执行计划中的一些常识. 2.能够分析执行计划,找到优化sql性能的思路或方案. 如果你对sql查询优化的理解或常识不是很深入,那么推荐几骗博 ...

随机推荐

  1. 【Rust】Rust的安装和配置

    -----------------------参考文档------------------------------------- https://www.rust-lang.org/tools/ins ...

  2. 微软官方关于 Windows To Go 的常见问题

    Windows To Go:常见问题 2016/04/01 本文内容 什么是 Windows To Go? Windows To Go 是否依赖虚拟化? 哪些人员应该使用 Windows To Go? ...

  3. 百度前端技术学院task15源代码

    这一道题涉及到排序,读取页面内容,输出显示到某一节点当中以及添加事件. 刚开始一直在想怎么获取某一节点的内容,后面采用的是sdata.childNodes,获取所有的节点.再通过schildNode[ ...

  4. 部署CentOS虚拟机集群

    1.在虚拟机中安装CentOS (1)使用CentOS-6.5-i386-minimal.iso.(2)创建虚拟机:打开Virtual Box,点击“新建”按钮,点击“下一步”,输入虚拟机名称为esh ...

  5. VS代码调试出现:当前不会命中断点。还没有为该文档加载任何符号。

    第一步:一定要检查最顶部自己设置的是 Release模式还是Debug模式!!!下面这个图就是在我搜了好多解决方式之后,突然发现自己开的是Release模式!!!吐血. 第二步:如果你已经确定了自己是 ...

  6. docker学习之路-build asp.net core 2.2产生 warning MSB3245: Could not resolve this reference.错误的解决办法

    在docker build的时候有时我们可以直接使用dotnet publish来发布,但是如果用docker构建镜像的时候却会出现下面的错误: 解决办法:https://stackoverflow. ...

  7. angularJS中select元素的应用浅析

    select array 数据: select ng-model 用法: 1.可以是一个对象形式,ng-model="test" $scope.test = {name: &quo ...

  8. Django:RestFramework之-------路由

    11.路由 路由设置: url(r'^(?P<version>[v1|v2]+)/vview\.(?P<format>\w+)$', views.VView.as_view({ ...

  9. android启动时间慢的问题

     [转]对于Android的性能这方面评估,大部分都是有超级兔子去比跑分的,还是不能反映全面的问题.就我知道的而言,应用启动时间是很影响用户体验的一个性能方面问题. 最近的一个项目,别人都说应用启动慢 ...

  10. 使用代码获得Hybris Commerce里显示的产品图片

    使用下面这个API去取Hybris Commerce系统里产品主数据的明细信息: https://:9002/rest/v2/electronics/products/300938?fields=FU ...