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的更多相关文章

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

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

  2. postgresql中执行计划

    1.Explain explain select * from tablename; 2.explain输出josn格式 explain (format json) select * from tab ...

  3. PostgreSQL 执行计划

    简介 PostgreSQL是“世界上最先进的开源关系型数据库”.因为出现较晚,所以客户人群基数较MySQL少,但是发展势头很猛,最大优势是完全开源. MySQL是“世界上最流行的开源关系型数据库”.当 ...

  4. PostgreSQL执行计划的解析

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

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

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

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

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

  7. postgreSQL执行计划

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

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

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

  9. SELECT TOP 1 比不加TOP 1 慢的原因分析以及SELECT TOP 1语句执行计划预估原理

    本文出处:http://www.cnblogs.com/wy123/p/6082338.html 现实中遇到过到这么一种情况: 在某些特殊场景下:进行查询的时候,加了TOP 1比不加TOP 1要慢(而 ...

随机推荐

  1. 国外价值10K+美金的Python面试题,珍藏已久,含泪放了出来

    兄弟们,没吹牛皮,一哥们在国外面试的时候,就是要他做的这个,直接给他说,做出来了给你15K(单位是刀),做不出来就拜拜~ 大兄弟当时就不服了,这不是看不起我么,分分钟就给整完了~ 我上我也行系列: 唠 ...

  2. Work Center View * cannot be used for report assignment. Please deselect错误解决方法

    by zyi

  3. P3480 [POI2009]KAM-Pebbles 题解

    题目链接 首先,这道题看上去就是个博弈论,很显然的 \(Nim\) 游戏. 因为每一个的取法都和它的上一位有关. 有一种非常显然的转换方式 :我们把这若干堆石子从前向后做一个差分 . 我们记 \(a_ ...

  4. NIO.2中Path、 Paths、Files类的使用

  5. ModuleNotFoundError: No module named 'distutils.spawn'

    解决办法: 安装python3-distutils sudo apt-get install python3-distutils

  6. springboot和mybatis 配置多数据源

    主数据源(由于代码没有办法复制的原因,下面图片和文字不一致) package com.zhianchen.mysqlremark.toword.config;import com.zaxxer.hik ...

  7. 记一次react-hooks项目获取图表图片集合并生成pdf的需求

    需求: 获取子组件中所有图片的dom元素并生成图片,再把生成的图片转化为pdf下载 难点 众所周知,react是单向数据流,倡导f(data)⇒ UI的哲学, 并不建议过多直接操作dom,但是生成图片 ...

  8. day03_3_流程控制练习题

    # 流程控制练习题 # 一.编程题 1.实现一个课程名称和课程代号的转换器:输入下表中的课程代号,输出课程的名称.用户可以循环进行输入,如果输入0就退出系统.(**使用****switch +whil ...

  9. 【AcWing】周赛

    A.糖果 题目链接 链接 题目描述 给定三个正整数 a,b,c. 请计算 ⌊a+b+c2⌋,即 a,b,c 相加的和除以 2 再下取整的结果. 输入格式 第一行包含整数 T,表示共有 T 组测试数据. ...

  10. CF1593D2 Half of Same

    题目大意: 给定一个包含 \(n\)(\(n\) 是偶数)个整数的数列 \(a_1,a_2,\ldots,a_n\). 考虑一个可能的正整数 \(k\),在每次操作中,你可以选定一个 \(i\),并将 ...