PostgreSQL 修改执行计划 GroupAggregate 为 HashAggregate
1、前言
PostgreSQL 聚合算法有两种,HashAggregate and GroupAggregate 。我们知道GroupAggregate 需要对记录进行排序,而 HashAggregate 则无需进行排序,通常 HashAggregate 要快很多。 但是,我们经常会看到优化器使用 GroupAggregate,即使 enable_sort=off 也不能阻止 排序操作。那么有什么办法让优化器使用 HashAggregate 了?
2、优化例子
创建测试数据:数据每个列有100个不同值,但是列值之间是相关的,也就是 count(distinct *) 也就100 个。
create table t1
(
id1 integer, id2 integer, id3 integer, id4 integer,
id5 integer, id6 integer, id7 integer, id8 integer,
status1 char(1), status2 char(1), status3 char(1), status4 char(1),
status5 char(1), status6 char(1), status7 char(1), status8 char(1),
id9 integer, id10 integer,
name text
); insert into t1
select
generate_series(1,100), generate_series(1,100), generate_series(1,100), generate_series(1,100),
generate_series(1,100), generate_series(1,100), generate_series(1,100), generate_series(1,100),
round(random()), round(random()), round(random()), round(random()),
round(random()), round(random()), round(random()), round(random()),
generate_series(1,100), generate_series(1,100),
repeat('a',100); begin
for i in 1..16 loop
insert into t1 select * from t1;
end loop;
end; test=# select count(*) from t1;
count
---------
6553600
(1 row)
不同的group by 走不同 aggregrate 算法。
test=# explain analyze select id1,count(*) from t1 group by id1;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=224335.18..224336.18 rows=100 width=12) (actual time=2084.369..2084.387 rows=100 loops=1)
Group Key: id1
-> Seq Scan on t1 (cost=0.00..191567.12 rows=6553612 width=4) (actual time=1.043..876.510 rows=6553600 loops=1)
Planning Time: 0.051 ms
Execution Time: 2084.422 ms
(5 rows) test=# explain analyze select id1,id2,count(*) from t1 group by id1,id2;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=240719.21..240819.21 rows=10000 width=16) (actual time=4801.232..4801.302 rows=100 loops=1)
Group Key: id1, id2
-> Seq Scan on t1 (cost=0.00..191567.12 rows=6553612 width=8) (actual time=0.017..3457.207 rows=6553600 loops=1)
Planning Time: 0.076 ms
Execution Time: 4801.430 ms
(5 rows) --随着 group by 列的数量增加,优化器开始走 GroupAggregate
test=# explain analyze select id1,id2,id3,count(*) from t1 group by id1,id2,id3;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
GroupAggregate (cost=1157569.44..1246043.20 rows=655361 width=20) (actual time=4519.386..6399.677 rows=100 loops=1)
Group Key: id1, id2, id3
-> Sort (cost=1157569.44..1173953.47 rows=6553612 width=12) (actual time=4501.004..5619.469 rows=6553600 loops=1)
Sort Key: id1, id2, id3
Sort Method: external merge Disk: 141144kB
-> Seq Scan on t1 (cost=0.00..191567.12 rows=6553612 width=12) (actual time=0.492..1274.615 rows=6553600 loops=1)
Planning Time: 0.072 ms
Execution Time: 6415.101 ms
(8 rows) test=# explain analyze select id1,id2,id3,id4,count(*) from t1 group by id1,id2,id3,id4;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
GroupAggregate (cost=1157569.44..1262427.23 rows=655361 width=24) (actual time=4873.150..7008.342 rows=100 loops=1)
Group Key: id1, id2, id3, id4
-> Sort (cost=1157569.44..1173953.47 rows=6553612 width=16) (actual time=4855.042..6153.156 rows=6553600 loops=1)
Sort Key: id1, id2, id3, id4
Sort Method: external merge Disk: 166792kB
-> Seq Scan on t1 (cost=0.00..191567.12 rows=6553612 width=16) (actual time=0.403..1320.904 rows=6553600 loops=1)
Planning Time: 0.208 ms
Execution Time: 7023.674 ms
(8 rows)
问题解析:根据优化器的估算,如果 group by 只有一列 ,有 100 个不同值,2列 100*100 个, 3列 100*100*100 个,也就是group by 后面的列越多,优化器会认为返回的不同值越多,优化器更偏向于 采用GroupAggregate算法。基于以上思路,我们考虑对表创建多列的统计数据。
test=# create statistics s1(ndistinct) on id1,id2,id3,id4,id5,id6,id7,id8 from t1;
CREATE STATISTICS
test=# analyze t1;
ANALYZE
test=# explain analyze select id1,id2,id3,id4,count(*) from t1 group by id1,id2,id3,id4;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=273487.27..273488.27 rows=100 width=24) (actual time=2787.681..2787.695 rows=100 loops=1)
Group Key: id1, id2, id3, id4
-> Seq Scan on t1 (cost=0.00..191567.12 rows=6553612 width=16) (actual time=0.024..926.445 rows=6553600 loops=1)
Planning Time: 0.243 ms
Execution Time: 2787.764 ms
(5 rows) test=# explain analyze select id1,id2,id3,id4,id5,id6,id7,id8,count(*) from t1 group by id1,id2,id3,id4,id5,id6,id7,id8;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=358628.65..358629.65 rows=100 width=40) (actual time=3304.199..3304.218 rows=100 loops=1)
Group Key: id1, id2, id3, id4, id5, id6, id7, id8
-> Seq Scan on t1 (cost=0.00..211172.20 rows=6553620 width=32) (actual time=0.027..887.569 rows=6553600 loops=1)
Planning Time: 0.196 ms
Execution Time: 3304.256 ms
可以看到,对表创建多列统计数据后,优化器采用了 HashAggregate 算法,性能也得到了很大提升。
3、group by 超过8列的情况
test=# create statistics s2(ndistinct) on id1,id2,id3,id4,id5,id6,id7,id8,status1 from t1;
ERROR: cannot have more than 8 columns in statistics test=# explain analyze select id1,id2,id3,id4,id5,id6,id7,id8,status1,status2,status3,status4,status5,status6,status7,status8,count(*)
test-# from t1 group by id1,id2,id3,id4,id5,id6,id7,id8,status1,status2,status3,status4,status5,status6,status7,status8;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=489701.05..489957.05 rows=25600 width=56) (actual time=7595.743..7595.849 rows=100 loops=1)
Group Key: id1, id2, id3, id4, id5, id6, id7, id8, status1, status2, status3, status4, status5, status6, status7, status8
-> Seq Scan on t1 (cost=0.00..211172.20 rows=6553620 width=48) (actual time=0.500..945.677 rows=6553600 loops=1)
Planning Time: 0.887 ms
Execution Time: 7596.065 ms
(5 rows) test-# select id1,id2,id3,id4,id5,id6,id7,id8,status1,status2,status3,status4,status5,status6,status7,status8,id9,count(*)
test-# from t1 group by id1,id2,id3,id4,id5,id6,id7,id8,status1,status2,status3,status4,status5,status6,status7,status8,id9;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
GroupAggregate (cost=1407916.97..1725760.65 rows=655348 width=60) (actual time=48694.618..68534.965 rows=100 loops=1)
Group Key: id1, id2, id3, id4, id5, id6, id7, id8, status1, status2, status3, status4, status5, status6, status7, status8, id9
-> Sort (cost=1407916.97..1424300.66 rows=6553478 width=52) (actual time=48542.144..64549.985 rows=6553600 loops=1)
Sort Key: id1, id2, id3, id4, id5, id6, id7, id8, status1, status2, status3, status4, status5, status6, status7, status8, id9
Sort Method: external merge Disk: 397656kB
-> Seq Scan on t1 (cost=0.00..217944.78 rows=6553478 width=52) (actual time=0.018..2029.935 rows=6553600 loops=1)
Planning Time: 0.203 ms
Execution Time: 68559.728 ms
(8 rows)
结论:
例子1:虽然无法创建超过8列的statistics,但由于后面的status 列的 distinct 值较少,8 个列优化器估算的distinct 值也只有 100 * 2^8 ,因此,优化器还是走 HashAggregate。
例子2:多了1列,估算的 distinct 值 100 * 2^8 *100 ,优化器走GroupAggregate
test=# explain analyze
test-# select id1,id2,id3,id4,id5,id6,id7,id8,id9,id10,status1,status2,status3,status4,status5,status6,count(*)
test-# from t1 group by id1,id2,id3,id4,id5,id6,id7,id8,id9,id10,status1,status2,status3,status4,status5,status6;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
GroupAggregate (cost=1407950.79..1709417.77 rows=655363 width=60) (actual time=36920.441..52070.524 rows=100 loops=1)
Group Key: id1, id2, id3, id4, id5, id6, id7, id8, id9, id10, status1, status2, status3, status4, status5, status6
-> Sort (cost=1407950.79..1424334.87 rows=6553630 width=52) (actual time=36801.599..48682.412 rows=6553600 loops=1)
Sort Key: id1, id2, id3, id4, id5, id6, id7, id8, id9, id10, status1, status2, status3, status4, status5, status6
Sort Method: external merge Disk: 397656kB
-> Seq Scan on t1 (cost=0.00..217946.30 rows=6553630 width=52) (actual time=0.098..2682.113 rows=6553600 loops=1)
Planning Time: 0.323 ms
Execution Time: 52096.144 ms
(8 rows) test=# create statistics s2(ndistinct) on id9,id10,status1,status2,status3,status4,status5,status6 from t1;
CREATE STATISTICS
test=# analyze t1; ANALYZE
test-# select id1,id2,id3,id4,id5,id6,id7,id8,id9,id10,status1,status2,status3,status4,status5,status6,status7,count(*)
test-# from t1 group by id1,id2,id3,id4,id5,id6,id7,id8,id9,id10,status1,status2,status3,status4,status5,status6,status7;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=512859.65..513059.65 rows=20000 width=62) (actual time=7189.342..7189.445 rows=100 loops=1)
Group Key: id1, id2, id3, id4, id5, id6, id7, id8, id9, id10, status1, status2, status3, status4, status5, status6, status7
-> Seq Scan on t1 (cost=0.00..217946.30 rows=6553630 width=54) (actual time=0.017..931.265 rows=6553600 loops=1)
Planning Time: 0.135 ms
Execution Time: 7189.517 ms
(5 rows)
结论:
例子1:由于distinct 值过多,优化器还是走GroupAggregate;
例子2:通过创建额外的 Statistics s2 , 可以走HashAggregate的情况,也就是s1 , s2 可以同时被优化器所使用。
4、结论
优化器在决定采用哪种 group by 算法时,会估算结果分组个数,如果结果分组个数过多,优化器会偏向于 GroupAggregate。但是我们知道,PostgreSQL 对于多列值的合并估算并不准确,这就需要手动创建多列统计信息。实际优化过程中,可能经常需要创建多列统计信息,以促使优化器采用HashAggregate。
PostgreSQL 修改执行计划 GroupAggregate 为 HashAggregate的更多相关文章
- PostgreSQL EXPLAIN执行计划学习--多表连接几种Join方式比较
转了一部分.稍后再修改. 三种多表Join的算法: 一. NESTED LOOP: 对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择.在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表 ...
- postgresql中执行计划
1.Explain explain select * from tablename; 2.explain输出josn格式 explain (format json) select * from tab ...
- PostgreSQL 执行计划
简介 PostgreSQL是“世界上最先进的开源关系型数据库”.因为出现较晚,所以客户人群基数较MySQL少,但是发展势头很猛,最大优势是完全开源. MySQL是“世界上最流行的开源关系型数据库”.当 ...
- PostgreSQL执行计划的解析
一个顺序磁盘页面操作的cost值由系统参数seq_page_cost (floating point)参数指定的,由于这个参数默认为1.0,所以我们可以认为一次顺序磁盘页面操作的cost值为1.下面o ...
- PostgreSQL执行计划:Bitmap scan VS index only scan
之前了解过postgresql的Bitmap scan,只是粗略地了解到是通过标记数据页面来实现数据检索的,执行计划中的的Bitmap scan一些细节并不十分清楚.这里借助一个执行计划来分析bitm ...
- PostgreSQL 与 Oracle 访问分区表执行计划差异
熟悉Oracle 的DBA都知道,Oracle 访问分区表时,对于没有提供分区条件的,也就是在无法使用分区剪枝情况下,优化器会根据全局的统计信息制定执行计划,该执行计划针对所有分区适用.在分析利弊之前 ...
- postgreSQL执行计划
" class="wiz-editor-body wiz-readonly" contenteditable="false"> explain命 ...
- Postgresql_根据执行计划优化SQL
执行计划路径选择 postgresql查询规划过程中,查询请求的不同执行方案是通过建立不同的路径来表达的,在生成许多符合条件的路径之后,要从中选择出代价最小的路径,把它转化为一个计划,传递给执行器执行 ...
- SELECT TOP 1 比不加TOP 1 慢的原因分析以及SELECT TOP 1语句执行计划预估原理
本文出处:http://www.cnblogs.com/wy123/p/6082338.html 现实中遇到过到这么一种情况: 在某些特殊场景下:进行查询的时候,加了TOP 1比不加TOP 1要慢(而 ...
随机推荐
- Vue回炉重造之封装防刷新考试倒计时组件
<!-- 考试倒计时组件 --> <template> <div class="time"> <p>00:{{timerCount2 ...
- Vue3.0系列——「vue3.0性能是如何变快的?」
前言 先学习vue2.x,很多2.x内容依然保留: 先学习TypeScript,vue3.0是用TS重写的,想知其然知其所以然必须学习TS. 为什么学习vue3.0? 性能比vue2.x快1.2-2倍 ...
- 安装ImageMagick7.1库以及php的Imagick扩展
由于ImageMagick7以下不支持heic等图片格式,所以重新安装了ImageMagick7.1版本支持heic格式,并写此文章记录一下. 如果安装过程中遇到一些未知的错误,https://ima ...
- SAP Web Dynpro-集成消息
您可以使用消息管理器将消息集成到消息日志中. 您可以使用Web Dynpro代码向导打开消息管理器. 您可以从工具栏中打开Web Dynpro代码向导. 当您的ABAP工作台处于更改模式或编辑视图或控 ...
- 158_模型_Power BI 使用 DAX + SVG 打通制作商业图表几乎所有可能
158_模型_Power BI 使用 DAX + SVG 打通制作商业图表几乎所有可能 一.背景 最近对 Power BI 中使用 SVG 比较感兴趣,今天我们使用 DAX + SVG 复刻一下 Ze ...
- Maven-打包jar指定main函数所在类的一个例子
问题描述:maven打包jar时,由于带main方法的类没有被加入manifest中,导致执行java -jar mvn-jar-1.0-SNAPSHOT.jar时,会提示没有主清单属性. 解决办法: ...
- 手把手教你用 Python 下载手机小视频
今天为大家介绍使用 mitmproxy 这个抓包工具如何监控手机上网,并且通过抓包,把我们想要的数据下载下来. 启动 mitmproxy 首先我们通过执行命令 mitmweb 启动mitmproxy, ...
- 编译调试Net6源码
前言 编辑调试DotNet源码可按照官网教程操作,但因为网络问题中间会出现各种下载失败的问题,这里出个简单的教程(以6为版本) 下载源码 下载源码 GitHub下载源码速度极慢,可替换为国内仓库htt ...
- 强化学习-学习笔记14 | 策略梯度中的 Baseline
本篇笔记记录学习在 策略学习 中使用 Baseline,这样可以降低方差,让收敛更快. 14. 策略学习中的 Baseline 14.1 Baseline 推导 在策略学习中,我们使用策略网络 \(\ ...
- Redis三种模式——主从复制,哨兵模式,集群
一.Redis主从复制作用 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复:实际上是一种服务的冗余. 负 ...