DuckDB对group by cube / rollup / groupping sets查询的优化

2026年6月16日,DuckDB合并了一个推送请求:https://github.com/duckdb/duckdb/pull/23287,与我以前的思路(https://blog.csdn.net/l1t/article/details/148504369 https://blog.csdn.net/l1t/article/details/148504384)一致。

ROLLUP / CUBE / Grouping Sets 的级联聚合优化

目前,ROLLUP / CUBE / Grouping Sets 的实现方式是使用多个哈希表。然而,它们通常可以通过级联聚合来实现。与其为每个分组集使用一个哈希表,我们可以先计算"最宽"的哈希表,然后将该哈希表作为输入,用于后续的聚合计算,依此类推。

例如,假设我们有如下查询:

sql 复制代码
SELECT l_returnflag, l_linestatus, SUM(l_quantity)
FROM lineitem
GROUP BY CUBE(l_returnflag, l_linestatus)
ORDER BY ALL;

实际上,我们需要计算四个分组集:

  • l_returnflag, l_linestatus
  • l_returnflag
  • l_linestatus
  • (空) // 即未分组

通过采用级联分组,我们可以节省大量时间------第一个哈希表 l_returnflag, l_linestatus 只有四行数据。因此,我们不需要将 lineitem 中的每一行都推入四个不同的哈希表,而只需将其推入一个哈希表,然后在该哈希表的基础上进行重新聚合即可。

利用物化 CTE 和聚合状态,我们具备了实现这一优化所需的所有组件。上述查询可以有效地重写为:

sql 复制代码
with 
	group_by_returnflag_linestatus as (
		select l_returnflag, l_linestatus, sum(l_quantity) export_state l_quantity_state from lineitem group by l_returnflag, l_linestatus
	),
	group_by_returnflag as (
		select l_returnflag, NULL AS l_linestatus, combine_aggr(l_quantity_state) l_quantity_state from group_by_returnflag_linestatus group by l_returnflag		
	),
	group_by_linestatus as (
		select NULL AS l_returnflag, l_linestatus, combine_aggr(l_quantity_state) l_quantity_state from group_by_returnflag_linestatus group by l_linestatus		
	),
	ungrouped as (
		select NULL AS l_returnflag, NULL AS l_linestatus, combine_aggr(l_quantity_state) l_quantity_state from group_by_returnflag
	)
SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM group_by_returnflag_linestatus
UNION ALL
SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM group_by_returnflag
UNION ALL
SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM group_by_linestatus
UNION ALL
SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM ungrouped;

实际上,我们先计算第一次聚合,并导出聚合状态------然后在该聚合结果的基础上进行"级联"聚合。最后,通过完成(finalize)已收集的聚合状态,即可得到最终结果。

在上述示例中,使用 CUBE 后我们获得了大约 2 倍的性能提升(TPC-H SF100):

版本 耗时(秒)
v1.5 0.8s
新版本 0.4s

我从相应的自动构建

https://github.com/duckdb/duckdb/actions/runs/27599923021 中提取的二进制文件,重命名为duckdb0616。

测试结果如下:

sql 复制代码
C:\d>duckdb0616
DuckDB v1.6.0-dev8751 (Development Version, 99fa1b5f55)
Enter ".help" for usage hints.
memory D CREATE TABLE lineitem(l_orderkey BIGINT NOT NULL, l_partkey BIGINT NOT NULL, l_suppkey BIGINT NOT NULL, l_linenumber BIGINT NOT NULL, l_quantity DECIMAL(15,2) NOT NULL, l_extendedprice DECIMAL(15,2) NOT NULL, l_discount DECIMAL(15,2) NOT NULL, l_tax DECIMAL(15,2) NOT NULL, l_returnflag VARCHAR NOT NULL, l_linestatus VARCHAR NOT NULL, l_shipdate DATE NOT NULL, l_commitdate DATE NOT NULL, l_receiptdate DATE NOT NULL, l_shipinstruct VARCHAR NOT NULL, l_shipmode VARCHAR NOT NULL, l_comment VARCHAR NOT NULL);
memory D COPY lineitem FROM 'tpch1/lineitem.csv' (FORMAT 'csv', force_not_null ('l_orderkey', 'l_partkey', 'l_suppkey', 'l_linenumber', 'l_quantity', 'l_extendedprice', 'l_discount', 'l_tax', 'l_returnflag', 'l_linestatus', 'l_shipdate', 'l_commitdate', 'l_receiptdate', 'l_shipinstruct', 'l_shipmode', 'l_comment'), quote '"', delimiter ',', header 1);
memory D .timer on
memory D SELECT l_returnflag, l_linestatus, SUM(l_quantity)
         FROM lineitem
         GROUP BY CUBE(l_returnflag, l_linestatus)
         ORDER BY ALL;
┌──────────────┬──────────────┬─────────────────┐
│ l_returnflag │ l_linestatus │ sum(l_quantity) │
│   varchar    │   varchar    │  decimal(38,2)  │
├──────────────┼──────────────┼─────────────────┤
│ A            │ F            │      3774200.00 │
│ A            │ NULL         │      3774200.00 │
│ N            │ F            │        95257.00 │
│ N            │ O            │      7679822.00 │
│ N            │ NULL         │      7775079.00 │
│ R            │ F            │      3785523.00 │
│ R            │ NULL         │      3785523.00 │
│ NULL         │ F            │      7654980.00 │
│ NULL         │ O            │      7679822.00 │
│ NULL         │ NULL         │     15334802.00 │
└──────────────┴──────────────┴─────────────────┘
  10 rows                             3 columns
Run Time (s): real 0.026 user 0.015625 sys 0.015625
memory D with
             group_by_returnflag_linestatus as (
                 select l_returnflag, l_linestatus, sum(l_quantity) export_state l_quantity_state from lineitem group by l_returnflag, l_linestatus
             ),
             group_by_returnflag as (
                 select l_returnflag, NULL AS l_linestatus, combine_aggr(l_quantity_state) l_quantity_state from group_by_returnflag_linestatus group by l_returnflag
             ),
             group_by_linestatus as (
                 select NULL AS l_returnflag, l_linestatus, combine_aggr(l_quantity_state) l_quantity_state from group_by_returnflag_linestatus group by l_linestatus
             ),
             ungrouped as (
                 select NULL AS l_returnflag, NULL AS l_linestatus, combine_aggr(l_quantity_state) l_quantity_state from group_by_returnflag
             )
         SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM group_by_returnflag_linestatus
         UNION ALL
         SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM group_by_returnflag
         UNION ALL
         SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM group_by_linestatus
         UNION ALL
         SELECT l_returnflag, l_linestatus, finalize(l_quantity_state) AS l_quantity_sum FROM ungrouped;
┌──────────────┬──────────────┬────────────────┐
│ l_returnflag │ l_linestatus │ l_quantity_sum │
│   varchar    │   varchar    │ decimal(38,2)  │
├──────────────┼──────────────┼────────────────┤
│ A            │ F            │     3774200.00 │
│ N            │ F            │       95257.00 │
│ N            │ O            │     7679822.00 │
│ R            │ F            │     3785523.00 │
│ R            │ NULL         │     3785523.00 │
│ A            │ NULL         │     3774200.00 │
│ N            │ NULL         │     7775079.00 │
│ NULL         │ F            │     7654980.00 │
│ NULL         │ O            │     7679822.00 │
│ NULL         │ NULL         │    15334802.00 │
└──────────────┴──────────────┴────────────────┘
  10 rows                            3 columns
Run Time (s): real 0.022 user 0.015625 sys 0.000000
memory D

memory D explain SELECT l_returnflag, l_linestatus, SUM(l_quantity)
         FROM lineitem
         GROUP BY CUBE(l_returnflag, l_linestatus)
         ORDER BY ALL;

┌─────────────────────────────┐
│┌───────────────────────────┐│
││       Physical Plan       ││
│└───────────────────────────┘│
└─────────────────────────────┘
┌───────────────────────────┐
│          ORDER_BY         │
│    ────────────────────   │
│      l_returnflag ASC     │
│      l_linestatus ASC     │
│    sum(l_quantity) ASC    │
│                           │
│       ~600,573 rows       │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│            CTE            │
│    ────────────────────   │
│         CTE Name:         │
│   __grouping_sets_cte_9   │
│                           ├──────────────┐
│       Table Index: 9      │              │
│                           │              │
│       ~600,573 rows       │              │
└─────────────┬─────────────┘              │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         PROJECTION        ││            CTE            │
│    ────────────────────   ││    ────────────────────   │
│__internal_decompress_strin││         CTE Name:         │
│           g(#0)           ││   __grouping_sets_cte_16  │
│__internal_decompress_strin││                           ├──────────────┐
│           g(#1)           ││      Table Index: 16      │              │
│             #2            ││                           │              │
│                           ││                           │              │
│       ~300,286 rows       ││       ~600,573 rows       │              │
└─────────────┬─────────────┘└─────────────┬─────────────┘              │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│   PERFECT_HASH_GROUP_BY   ││       HASH_GROUP_BY       ││           UNION           │
│    ────────────────────   ││    ────────────────────   ││                           │
│          Groups:          ││         Groups: #0        ││                           │
│             #0            ││                           ││                           │
│             #1            ││        Aggregates:        ││                           │
│                           ││      combine_aggr(#1)     ││                           ├──────────────┬────────────────────────────┬────────────────────────────┐
│        Aggregates:        ││                           ││                           │              │                            │                            │
│    sum(#2) EXPORT_STATE   ││                           ││                           │              │                            │                            │
│                           ││                           ││                           │              │                            │                            │
│       ~300,286 rows       ││       ~150,143 rows       ││                           │              │                            │                            │
└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘              │                            │                            │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         PROJECTION        ││         PROJECTION        ││         PROJECTION        ││         PROJECTION        ││         PROJECTION        ││         PROJECTION        │
│    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
│        l_returnflag       ││             #1            ││            NULL           ││             #0            ││             #0            ││            NULL           │
│        l_linestatus       ││             #2            ││            NULL           ││            NULL           ││             #1            ││             #0            │
│         l_quantity        ││                           ││        finalize(#0)       ││        finalize(#1)       ││        finalize(#2)       ││        finalize(#1)       │
│                           ││                           ││                           ││                           ││                           ││                           │
│       ~600,572 rows       ││       ~300,286 rows       ││           ~1 row          ││       ~150,143 rows       ││       ~300,286 rows       ││       ~150,143 rows       │
└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         PROJECTION        ││          CTE_SCAN         ││    UNGROUPED_AGGREGATE    ││       HASH_GROUP_BY       ││          CTE_SCAN         ││          CTE_SCAN         │
│    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
│__internal_compress_string_││        CTE Index: 9       ││        Aggregates:        ││         Groups: #0        ││        CTE Index: 9       ││       CTE Index: 16       │
│        utinyint(#0)       ││                           ││      combine_aggr(#0)     ││                           ││                           ││                           │
│__internal_compress_string_││                           ││                           ││        Aggregates:        ││                           ││                           │
│        utinyint(#1)       ││                           ││                           ││      combine_aggr(#1)     ││                           ││                           │
│             #2            ││                           ││                           ││                           ││                           ││                           │
│                           ││                           ││                           ││                           ││                           ││                           │
│       ~600,572 rows       ││       ~300,286 rows       ││           ~1 row          ││       ~150,143 rows       ││       ~300,286 rows       ││       ~150,143 rows       │
└─────────────┬─────────────┘└───────────────────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└───────────────────────────┘└───────────────────────────┘
┌─────────────┴─────────────┐                             ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│          SEQ_SCAN         │                             │         PROJECTION        ││         PROJECTION        │
│    ────────────────────   │                             │    ────────────────────   ││    ────────────────────   │
│           Table:          │                             │             #1            ││             #0            │
│    memory.main.lineitem   │                             │                           ││             #2            │
│                           │                             │                           ││                           │
│   Type: Sequential Scan   │                             │                           ││                           │
│                           │                             │                           ││                           │
│        Projections:       │                             │                           ││                           │
│        l_returnflag       │                             │                           ││                           │
│        l_linestatus       │                             │                           ││                           │
│         l_quantity        │                             │                           ││                           │
│                           │                             │                           ││                           │
│       ~600,572 rows       │                             │       ~150,143 rows       ││       ~300,286 rows       │
└───────────────────────────┘                             └─────────────┬─────────────┘└─────────────┬─────────────┘
                                                          ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
                                                          │          CTE_SCAN         ││          CTE_SCAN         │
                                                          │    ────────────────────   ││    ────────────────────   │
                                                          │       CTE Index: 16       ││        CTE Index: 9       │
                                                          │                           ││                           │
                                                          │       ~150,143 rows       ││       ~300,286 rows       │
                                                          └───────────────────────────┘└───────────────────────────┘
Run Time (s): real 0.005 user 0.000000 sys 0.000000
memory D .exit

从上面的执行计划可见,group by cube被自动转成了CTE查询。

sql 复制代码
memory D SELECT GROUPING_ID(), l_returnflag, l_linestatus, SUM(l_quantity)
         FROM lineitem
         GROUP BY CUBE(l_returnflag, l_linestatus)
         ORDER BY ALL;
┌────────────┬──────────────┬──────────────┬─────────────────┐
│ GROUPING() │ l_returnflag │ l_linestatus │ sum(l_quantity) │
│   int64    │   varchar    │   varchar    │  decimal(38,2)  │
├────────────┼──────────────┼──────────────┼─────────────────┤
│          0 │ A            │ F            │      3774200.00 │
│          0 │ N            │ F            │        95257.00 │
│          0 │ N            │ O            │      7679822.00 │
│          0 │ R            │ F            │      3785523.00 │
│          1 │ A            │ NULL         │      3774200.00 │
│          1 │ N            │ NULL         │      7775079.00 │
│          1 │ R            │ NULL         │      3785523.00 │
│          2 │ NULL         │ F            │      7654980.00 │
│          2 │ NULL         │ O            │      7679822.00 │
│          3 │ NULL         │ NULL         │     15334802.00 │
└────────────┴──────────────┴──────────────┴─────────────────┘
  10 rows                                          4 columns

比起手工方法,它提供了正确的GROUPING_ID()来辨别所属分组。

改用1.5.3版本

sql 复制代码
C:\d>duckdb153
DuckDB v1.5.3 (Variegata)
Enter ".help" for usage hints.
memory D CREATE TABLE lineitem(l_orderkey BIGINT NOT NULL, l_partkey BIGINT NOT NULL, l_suppkey BIGINT NOT NULL, l_linenumber BIGINT NOT NULL, l_quantity DECIMAL(15,2) NOT NULL, l_extendedprice DECIMAL(15,2) NOT NULL, l_discount DECIMAL(15,2) NOT NULL, l_tax DECIMAL(15,2) NOT NULL, l_returnflag VARCHAR NOT NULL, l_linestatus VARCHAR NOT NULL, l_shipdate DATE NOT NULL, l_commitdate DATE NOT NULL, l_receiptdate DATE NOT NULL, l_shipinstruct VARCHAR NOT NULL, l_shipmode VARCHAR NOT NULL, l_comment VARCHAR NOT NULL);
memory D COPY lineitem FROM 'tpch1/lineitem.csv' (FORMAT 'csv', force_not_null ('l_orderkey', 'l_partkey', 'l_suppkey', 'l_linenumber', 'l_quantity', 'l_extendedprice', 'l_discount', 'l_tax', 'l_returnflag', 'l_linestatus', 'l_shipdate', 'l_commitdate', 'l_receiptdate', 'l_shipinstruct', 'l_shipmode', 'l_comment'), quote '"', delimiter ',', header 1);
memory D explain SELECT l_returnflag, l_linestatus, SUM(l_quantity)
         FROM lineitem
         GROUP BY CUBE(l_returnflag, l_linestatus)
         ORDER BY ALL;

┌─────────────────────────────┐
│┌───────────────────────────┐│
││       Physical Plan       ││
│└───────────────────────────┘│
└─────────────────────────────┘
┌───────────────────────────┐
│          ORDER_BY         │
│    ────────────────────   │
│      l_returnflag ASC     │
│      l_linestatus ASC     │
│    sum(l_quantity) ASC    │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│       HASH_GROUP_BY       │
│    ────────────────────   │
│          Groups:          │
│             #0            │
│             #1            │
│                           │
│        Aggregates:        │
│    sum_no_overflow(#2)    │
│                           │
│          ~5 rows          │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         PROJECTION        │
│    ────────────────────   │
│        l_returnflag       │
│        l_linestatus       │
│         l_quantity        │
│                           │
│       ~600,572 rows       │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│          SEQ_SCAN         │
│    ────────────────────   │
│           Table:          │
│    memory.main.lineitem   │
│                           │
│   Type: Sequential Scan   │
│                           │
│        Projections:       │
│        l_returnflag       │
│        l_linestatus       │
│         l_quantity        │
│                           │
│       ~600,572 rows       │
└───────────────────────────┘
memory D

memory D .timer on
memory D SELECT l_returnflag, l_linestatus, SUM(l_quantity)
         FROM lineitem
         GROUP BY CUBE(l_returnflag, l_linestatus)
         ORDER BY ALL;
┌──────────────┬──────────────┬─────────────────┐
│ l_returnflag │ l_linestatus │ sum(l_quantity) │
│   varchar    │   varchar    │  decimal(38,2)  │
├──────────────┼──────────────┼─────────────────┤
│ A            │ F            │      3774200.00 │
│ A            │ NULL         │      3774200.00 │
│ N            │ F            │        95257.00 │
│ N            │ O            │      7679822.00 │
│ N            │ NULL         │      7775079.00 │
│ R            │ F            │      3785523.00 │
│ R            │ NULL         │      3785523.00 │
│ NULL         │ F            │      7654980.00 │
│ NULL         │ O            │      7679822.00 │
│ NULL         │ NULL         │     15334802.00 │
└──────────────┴──────────────┴─────────────────┘
  10 rows                             3 columns
Run Time (s): real 0.024 user 0.046875 sys 0.046875

大概是由于数据量小,时间差别不明显。

相关推荐
l1t2 小时前
DeepSeek总结的MariaDB 的 DuckDB 存储引擎
数据库·mariadb
tiancaijiben3 小时前
阿里云VMware服务完全对接指南:从环境准备到混合云生产级应用
数据库
Curvatureflight3 小时前
MySQL 深分页越来越慢?从 LIMIT OFFSET 改成游标分页
数据库·oracle
XZ-0700013 小时前
MySQL事务
数据库·mysql·oracle
tiancaijiben3 小时前
阿里云函数计算FC如何实现网站的定时任务与自动化
数据库·oracle·dba
xfhuangfu4 小时前
Oracle 19c 多租户体系架构介绍
数据库·oracle·架构
java1234_小锋4 小时前
请描述 Spring Boot 的启动流程,包括 SpringApplication 的初始化和 run 方法的核心步骤。
java·数据库·spring boot
qq_谁赞成_谁反对4 小时前
甲方IT的成长之路--nginx实战--2604
服务器·数据库·nginx