HashAggregate VS sort+GroupAgg

简介: postgresql聚合算法选择

什么是聚合

聚合就是把元素按照一定的规则分为不同的组,然后对各组元素进行计算。

如图:下面是通过颜色分组,然后计算sum。
_2018_10_23_4_09_08

postgresql中聚合算法

  • GroupAggregate: get the data sorted and apply aggregation function to groups one by one。
  • HashAggregate: store state for each key in a hash table

对比两种算法

_2018_10_23_4_17_40

GroupAggregate

GroupAggregatede 特点是在进行聚合之前先要将数据进行排序,然后进行聚合操作,而且出来的结果是有序的。(postgresql使用enable_sort控制)

HashAggregate

特点是不需要进行排序,在组数值比较小的情况下是比GroupAggregate要快很多,但是需求的内存会比较多。只能进行一些简单的聚合,像count(distinct ...)这类聚合是做不了的。


对比两种算法速率

创建9个测试表
create table t_1  (branch_id bigint, amount numeric);
create table t_10   (branch_id bigint, amount numeric);
create table t_100   (branch_id bigint, amount numeric);
create table t_1000   (branch_id bigint, amount numeric);
create table t_10000  (branch_id bigint, amount numeric);
create table t_100000 (branch_id bigint, amount numeric);
create table t_1000000 (branch_id bigint, amount numeric);
create table t_10000000 (branch_id bigint, amount numeric);
create table t_100000000 (branch_id bigint, amount numeric);

每个表的大小为1亿数据量,控制每张表组个数(其实就是distinct值)。
insert into t_1 select mod(i, 1), random()     from generate_series(1,100000000) s(i);
insert into t_10 select mod(i, 10), random()     from generate_series(1,100000000) s(i);
insert into t_100 select mod(i, 100), random()     from generate_series(1,100000000) s(i);
...
vacuum analyze t_1;
vacuum analyze t_10;
vacuum analyze t_100;
...

;

_2018_10_23_4_37_58

可以看到在组个数在10^7以下时,hashagg都要比groupagg优。其中hashagg的耗时也是跟着组个数逐渐升高的。当值达到10^8 时,执行计划会强制走groupagg,即使关闭了enable_sort.

大部分情况下hashagg的效率都会比groupagg要好,主要是sort这个操作比较耗时,本质上hashagg是在用空间(内存)换时间,内存充足的情况下这种做ok,内存不足是容易oom(pg在内存不足的情况下是会强制走groupagg)

原理解析

_2018_10_23_4_48_13
_2018_10_23_4_49_21

应用

应该尽量将groupagg转化为hashagg执行,大部分情况下执行器会自行选择最优算法,但在一些特定情况下,需要手动改写。

表t1

create table t1   (branch_id bigint, amount numeric);
insert into t1 select mod(i, 1000), random()     from generate_series(1,10000000) s(i);

SQL

select branch_id,count(distinct amount) from t1 group by branch_id;
postgres=# explain analyze select branch_id,count(distinct amount) from t1 group by branch_id;
                                                          QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1326385.18..1401396.05 rows=1000 width=16) (actual time=2524.329..8132.612 rows=1000 loops=1)
   Group Key: branch_id
   ->  Sort  (cost=1326385.18..1351385.47 rows=10000115 width=19) (actual time=2517.526..4227.713 rows=10000000 loops=1)
         Sort Key: branch_id
         Sort Method: quicksort  Memory: 1174466kB
         ->  Seq Scan on t1  (cost=0.00..163696.15 rows=10000115 width=19) (actual time=0.015..824.249 rows=10000000 loops=1)
 Planning time: 0.105 ms
 Execution time: 8234.834 ms
(8 rows)

现在需要转化为hashagg语句,强制关闭sort,发现执行计划还是没有改变,事实是hashagg无法处理count(distinct ...)这类聚合

postgres=# explain analyze select branch_id,count(distinct amount) from t1 group by branch_id;
                                                           QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=10001326385.18..10001401396.05 rows=1000 width=16) (actual time=2581.177..7953.331 rows=1000 loops=1)
   Group Key: branch_id
   ->  Sort  (cost=10001326385.18..10001351385.47 rows=10000115 width=19) (actual time=2574.178..4126.988 rows=10000000 loops=1)
         Sort Key: branch_id
         Sort Method: quicksort  Memory: 1174466kB
         ->  Seq Scan on t1  (cost=0.00..163696.15 rows=10000115 width=19) (actual time=0.015..813.226 rows=10000000 loops=1)
 Planning time: 0.082 ms
 Execution time: 8034.338 ms

修改语句

 select branch_id,count(*) from (select branch_id,amount from t1 group by branch_id,amount)_ group by branch_id;
postgres=# explain analyze select branch_id,count(*) from (select branch_id,amount from t1 group by branch_id,amount)_ group by branch_id;
                                                          QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=458272.50..458274.50 rows=200 width=16) (actual time=9544.638..9544.763 rows=1000 loops=1)
   Group Key: t1.branch_id
   ->  HashAggregate  (cost=213696.73..311527.04 rows=9783031 width=19) (actual time=5200.136..7961.864 rows=9999971 loops=1)
         Group Key: t1.branch_id, t1.amount
         ->  Seq Scan on t1  (cost=0.00..163696.15 rows=10000115 width=19) (actual time=0.022..890.532 rows=10000000 loops=1)
 Planning time: 0.197 ms
 Execution time: 9810.855 ms

走了hashagg之后时间并没有减少,why?根据上面的理论,时间应该减少才对,仔细看执行计划,时间上改写之后的语句进行了两次的hashagg,每次时间在4s左右。而且第一次hashagg之后,由于amount这个值的选择度太高,导致后面一次的hashagg数据量还是和前面一样,所以时间也用了4s左右。所以这种改写方法适合在amount选择度较低的时候,减少第二次的hashagg的时间。如果值选择性很高,原语句和耗时数一个sort+groupagg,改写之后的语句是2*hashagg。下面我们把amount的选择度降低,再来看一下效果。

update t1 set amount = branch_id;
postgres=# explain analyze select branch_id,count(distinct amount) from t1 group by branch_id;
                                                           QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=10002533944.49..10002672603.94 rows=1000 width=16) (actual time=3143.167..7858.782 rows=1000 loops=1)
   Group Key: branch_id
   ->  Sort  (cost=10002533944.49..10002580160.97 rows=18486593 width=19) (actual time=3137.152..4561.355 rows=10000000 loops=1)
         Sort Key: branch_id
         Sort Method: quicksort  Memory: 861967kB
         ->  Seq Scan on t1  (cost=0.00..302614.93 rows=18486593 width=19) (actual time=613.417..1537.574 rows=10000000 loops=1)
 Planning time: 0.134 ms
 Execution time: 7925.181 ms
(8 rows)

postgres=# explain analyze select branch_id,count(*) from (select branch_id,amount from t1 group by branch_id,amount)_ group by branch_id;
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=847179.99..847181.99 rows=200 width=16) (actual time=3987.460..3987.611 rows=1000 loops=1)
   Group Key: t1.branch_id
   ->  HashAggregate  (cost=395047.90..575900.73 rows=18085284 width=19) (actual time=3821.612..3986.812 rows=1000 loops=1)
         Group Key: t1.branch_id, t1.amount
         ->  Seq Scan on t1  (cost=0.00..302614.93 rows=18486593 width=19) (actual time=73.574..919.854 rows=10000000 loops=1)
 Planning time: 0.155 ms
 Execution time: 4438.999 ms
(7 rows)

可以看到amount值选择度变低之后,第一次hashagg的时间还是4s左右,但是第二次hashagg的时间明显减少了,所有改写之后的语句耗时要比原语句少

利用pg10并行,原语句是无法使用并行的,修改后的语句可以走并行,这里我们把并行度设置为4,开了并行之后默认是走sort+groupagg的执行计划

QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=408450.41..937838.27 rows=200 width=16) (actual time=831.472..1365.904 rows=1000 loops=1)
   Group Key: t1.branch_id
   ->  Group  (cost=408450.41..922858.98 rows=998486 width=12) (actual time=830.731..1365.377 rows=1000 loops=1)
         Group Key: t1.branch_id, t1.amount
         ->  Gather Merge  (cost=408450.41..902889.26 rows=3993944 width=12) (actual time=830.730..1364.782 rows=5000 loops=1)
               Workers Planned: 4
               Workers Launched: 4
               ->  Group  (cost=407950.35..426671.97 rows=998486 width=12) (actual time=794.142..1317.058 rows=1000 loops=5)
                     Group Key: t1.branch_id, t1.amount
                     ->  Sort  (cost=407950.35..414190.89 rows=2496215 width=12) (actual time=794.139..1002.323 rows=2000000 loops=5)
                           Sort Key: t1.branch_id, t1.amount
                           Sort Method: quicksort  Memory: 197526kB
                           ->  Parallel Seq Scan on t1  (cost=0.00..142711.15 rows=2496215 width=12) (actual time=35.558..190.117 rows=2000000 loops=5)
 Planning time: 0.119 ms
 Execution time: 1379.119 ms

从现在的现象看来,hashagg和groupagg的效率还是和许多因素相关的,一般情况下hashagg的效率要更高。在改写count(distinct ...)这类语句是要看值的选择性是不是很高。

参考链接1
参看链接2

相关文章
|
1月前
|
算法 定位技术
蓝牙室内定位核心技术解析:RSSI 与 AOA 的测距原理对比与精度升级逻辑
本文解析蓝牙室内定位核心技术,对比RSSI与AOA的测距原理,深入探讨从RSSI到AOA在精度、误差控制和硬件适配方面的升级逻辑,揭示两者如何互补满足不同场景需求。
|
关系型数据库 Linux 数据库
PostgreSQL源码编译安装
本节详细介绍了如何通过源码编译安装 PostgreSQL 17.6,涵盖从源码下载、依赖安装、配置编译参数、执行编译与安装、创建数据库用户与目录、初始化数据库,到配置 systemd 启动服务的完整流程。内容适用于多种 Linux 发行版,如 Rocky Linux、CentOS、openEuler、Ubuntu、Debian 等,并提供了常见错误的解决方法及一键安装脚本,帮助用户高效完成 PostgreSQL 的源码部署。
564 0
PostgreSQL源码编译安装
|
存储 SQL 分布式计算
从源码看Velox如何做序列化
从源码角度分析Velox做序列化和反序列化的过程
1359 0
|
11月前
|
SQL 分布式计算 Java
Spark SQL向量化执行引擎框架Gluten-Velox在AArch64使能和优化
本文摘自 Arm China的工程师顾煜祺关于“在 Arm 平台上使用 Native 算子库加速 Spark”的分享,主要内容包括以下四个部分: 1.技术背景 2.算子库构成 3.算子操作优化 4.未来工作
1519 0
|
SQL 安全 数据库
数据库系统的ACL
【8月更文挑战第13天】
410 1
|
监控 安全 网络安全
探讨网站加密访问的安全性问题:HTTPS的防护与挑战
**探讨HTTPS在网站加密中的角色,提供数据加密和身份验证,防范中间人攻击。心脏滴血漏洞示例显示持续维护的必要性。面临证书管理、性能影响和高级攻击挑战,应对措施包括更新、HSTS策略及用户教育。HTTPS是安全基础,但需不断优化以应对新威胁。**
836 2
|
数据采集 存储 NoSQL
爬虫在金融领域的应用:股票数据收集
本文探讨了网络爬虫在金融领域的应用,特别是在收集股票价格数据方面的实践。文章介绍了使用Scrapy框架和代理IP技术来构建爬虫,以应对反爬策略和提高数据采集效率。通过安装Scrapy和PyMongo,创建Scrapy项目,配置代理中间件,以及编写爬虫代码,实现了从Yahoo Finance抓取股票信息并存储至MongoDB。这种方法能有效助力市场分析和投资决策,提升数据采集的效率与质量。
1007 0
爬虫在金融领域的应用:股票数据收集
|
SQL 存储 监控
开源分布式数据库PolarDB-X源码解读——PolarDB-X源码解读(三):CDC代码结构
开源分布式数据库PolarDB-X源码解读——PolarDB-X源码解读(三):CDC代码结构
5851 0
|
存储 SQL 分布式计算
Velox表达式计算原理调研
velox是Meta开源的高性能的C++计算引擎,本文主要来调研下其表达式计算的实现原理。
1777 3