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. Windows-安装OpenVINO

    安装指导书链接: https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_wi ...

  2. SAP APO-需求计划

    需求计划可以对市场中的产品进行预测. 需求计划过程的输出就是需求计划,它考虑了影响需求的所有因素. 需求计划流程定义了需求计划周期中的活动. 由于需求计划过程以循环的形式进行,因此可以重复某些活动. ...

  3. Array 数组filter()方法,遍历数组返回符合条件的一个新数组

    <script> const arr = [ { id: 1, name: "aa", isDone: false }, { id: 2, name: "bb ...

  4. Feign通过自定义注解实现路径的转义

    本文主要讲解如果通过注解实现对路由中的路径进行自定义编码 背景 近期由于项目中需要,所以需要通过Feign封装一个对Harbor操作的sdk信息. 在调用的过程中发现,当请求参数中带有"/& ...

  5. MySQL十种锁,一篇文章带你全解析

    MySQL有两个核心的知识点,索引和锁.前几篇文章已经详细讲解了MySQL索引实现机制,今天再一起学习一下MySQL的锁. 1 为什么要加锁? 当多个事务并发操作同一批数据的时候,如果不加锁,就无法保 ...

  6. JavaWeb连接MySQL数据库

    JavaWeb连接MySQL数据库 JavaWeb连接MySQL数据库的方式有很多,首先我们讲解JDBC的配置方法 一.JDBC的配置方法 1.什么是JDBC 什么是JDBC嘞?JDBC代表Java数 ...

  7. 记一次 .NET 某电厂Web系统 内存泄漏分析

    一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...

  8. C++系统函数

    C++语言预先编写了很多常用函数提供给广大程序员使用,这些函数被统称为系统函数.C++语言全盘继承了C语言的标准C库,另外又增加了一些新的库(更多的是系统类库),这些新库被统称为C++标准库. 一.C ...

  9. 机器学习-Kmeans

    一.什么是聚类算法? 1.用于发现共同的群体(cluster),比如:邮件聚类.用户聚类.图片边缘. 2.聚类唯一会使用到的信息是:样本与样本之间的相似度(跟距离负相关) 给定N个训练样本(未标记的) ...

  10. 使用uni-app 地图组件

    首先,官方文档:https://uniapp.dcloud.io/component/map.html so,easy 但是没什么用~--~,太简单了 uni-app一般内置的使用的是腾讯地图,这个组 ...