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要慢(而 ...
随机推荐
- 【Pr】基础流程
新建工程 1.打开Pr 2.点击"新建""项目" 3.在电脑磁盘上新建好项目想要存放的位置,比如Demo1,为了便于管理,我先新建了一个Demo文件夹,再在里边 ...
- Go写文件的权限 WriteFile(filename, data, 0644)?
本文来自博客园,作者:阿伟的博客,转载请注明原文链接:https://www.cnblogs.com/cenjw/p/go-ioutil-writefile-perm.html 前言 go iouit ...
- Event Loop我知道,宏任务微任务是什么鬼?
在介绍宏任务和微任务之前,先抛出一个问题.相信大家在面试的时候,会遇到这样的相似的问题: setTimeout(function(){undefined console.log('1') }); ne ...
- 【WPF】CAD工程图纸转WPF可直接使用的xaml代码技巧
前言:随着工业化的进一步发展,制造业.工业自动化等多领域,都可能用到上位监控系统.而WPF在上位监控系统方面,应该算是当下最流行的前端框架之一了.而随着监控体系的不断完善与更新迭代,监控画面会变得越来 ...
- Timer和ScheduledThreadPoolExecutor的区别
Timer 基于单线程.系统时间实现的延时.定期任务执行类.具体可以看下面红色标注的代码. public class Timer { /** * The timer task queue. This ...
- String长度限制?
String我们在开发和学习中会经常用到,但对String类型的取值范围我们并不明确. String底层是char数组,并未标明长度限制.java中可以对数组指定长度,如果不指定就以实际元素来指定 p ...
- Learning Latent Graph Representations for Relational VQA
The key mechanism of transformer-based models is cross-attentions, which implicitly form graphs over ...
- CMP0065警告问题
参考链接: https://cmake.org/cmake/help/latest/policy/CMP0065.html https://cmake-developers.cmake.narkive ...
- 互联网产品前后端分离测试(Eolink 分享)
在互联网产品质量保障精细化的大背景下,根据系统架构从底层通过技术手段发起测试,显得尤为重要,测试分层的思想正是基于此产生的,目前也是较为成熟的测试策略. 一般采用自下而上的测试方式,以最简单的单一前后 ...
- [原创]移远RM500U-CN模组驱动移植
1. 简介 中国广电正式放号了,为了支持广电700MHz的5G基站,需要换用新的5G模组.移远通信的RM500U模组正好可以满足我们的使用要求; 我们选用该模组的原因:双卡单待 支持SIM卡热插拔 支 ...