博客 / 詳情

返回

PostgreSQL 19:超高速聚合的全新突破

PostgreSQL 18 正式發佈後,PostgreSQL 19 的性能改進方向已經引發廣泛關注。其中,聚合性能的重大優化被認為是最具突破性的改進之一,並且這一優化對現有應用完全透明,無需修改代碼、無需調整參數,即可直接生效。

PostgreSQL 中的數據聚合

在 PostgreSQL 此前的版本中,聚合的基本執行規則是:

先關聯(Join),後聚合(Aggregate)

典型示例如下:

SELECT     j.gender_name, count(*)
FROM    person AS p, gender AS j
WHERE    p.gender_id = j.gender_id
GROUP BY j.gender_name

在該類場景中,通常只存在少量維度數據(如性別類型),但主表數據規模可能達到百萬級。傳統執行邏輯如下:

  • 順序讀取 person 表中的每一條記錄
  • 依據 gender_id 逐條查找對應的 gender_name,並將結果累加到對應分組中
  • 輸出聚合結果

該方式在邏輯上並不存在錯誤,也是大多數數據庫系統的常規處理方式。但當數據呈現出“主表極大、維表極小”的典型特徵時,性能問題便會顯現:

  • 相同的維度值被反覆查找
  • 聚合性能隨數據規模下降

突破性改進:先聚合,後關聯

PostgreSQL 19 引入了一項關鍵優化能力:

執行計劃可在“先聚合,後關聯”與“先關聯,後聚合”之間自主選擇。

這一看似細微的調整,實則能帶來顛覆性的性能飛躍。

在大量業務系統中,以下結構極為常見:

CREATE TABLE t_category (
    category_id        int4    PRIMARY KEY,
    category_name        text
);

INSERT INTO t_category VALUES
    (0, 'Shoes'), (1, 'Shirts'),
    (2, 'Car'), (3, 'Bike');

CREATE TABLE t_color (
    color_id        int4    PRIMARY KEY,
    color_name        text
);

INSERT INTO t_color VALUES
    (0, 'Red'), (1, 'Green'),
    (2, 'Yellow'), (3, 'Blue');

CREATE TABLE t_product (
    category_id        int4    REFERENCES t_category (category_id),
    color_id        int4    REFERENCES t_color (color_id),
    whatever        text
);

該數據模型包含兩個極小的維度表(類別表、顏色表)和一個數據量巨大的產品表,本示例中產品表規模為 200,000 行:

INSERT INTO t_product
    SELECT    id % 4, (id * random())::int4 % 4, md5(id::text)
    FROM    generate_series(1, 200000) AS id;

目標是按“類別 + 顏色”統計產品數量,對應的 SQL 查詢語句如下:

SELECT    category_name, color_name, count(*)
FROM    t_product AS p, t_category AS c1, t_color AS c2
WHERE    p.color_id = c2.color_id
    AND c1.category_id = c1.category_id
GROUP BY 1, 2;

這是一個僅涉及三張數據表的關聯查詢,核心邏輯是針對每條產品記錄,查詢兩類維度名稱,PostgreSQL 19 之前的版本對應執行計劃如下:

   QUERY PLAN
------------------------------------------------------------------------------------------------------
 Finalize GroupAggregate  (cost=13167.09..13170.53 rows=16 width=18)
   Group Key: c1.category_name, c2.color_name
   ->  Gather Merge  (cost=13167.09..13170.17 rows=27 width=18)
        Workers Planned: 1
        ->  Sort  (cost=12167.08..12167.12 rows=16 width=18)
             Sort Key: c1.category_name, c2.color_name
             ->  Partial HashAggregate  (cost=12166.60..12166.76 rows=16 width=18)
                  Group Key: c1.category_name, c2.color_name
                  ->  Hash Join  (cost=2.49..8637.19 rows=470588 width=10)
                       Hash Cond: (p.color_id = c2.color_id)
                       ->  Parallel Seq Scan on t_product p  (cost=0.00..3046.47 rows=117647 width=4)
                       ->  Hash  (cost=2.29..2.29 rows=16 width=14)
                            ->  Nested Loop  (cost=0.00..2.29 rows=16 width=14)
                                 ->  Seq Scan on t_category c1  (cost=0.00..1.04 rows=4 width=5)
                                 ->  Materialize  (cost=0.00..1.06 rows=4 width=9)
                                      ->  Seq Scan on t_color c2  (cost=0.00..1.04 rows=4 width=9)
(16 rows)

分析該執行計劃需遵循從內向外的原則。執行流程以對顏色表和類別表的全表掃描為起點,隨後將維度表與產品主表完成關聯,待關聯操作全部結束後,才會啓動聚合計數。也就是説,系統需要針對每條產品記錄,重複執行兩次維度名稱查詢。

採用 PostgreSQL 19 新優化機制後的執行計劃如下:

QUERY PLAN
-----------------------------------------------------------------------------------------------------------
 Finalize GroupAggregate  (cost=4636.63..4638.60 rows=15 width=18)
  Group Key: c1.category_name, c2.color_name
  ->  Gather Merge  (cost=4636.63..4638.34 rows=15 width=18)
       Workers Planned: 1
       ->  Sort  (cost=3636.62..3636.64 rows=9 width=18)
            Sort Key: c1.category_name, c2.color_name
            ->  Nested Loop  (cost=3634.84..3636.48 rows=9 width=18)
                 ->  Nested Loop  (cost=3634.84..3635.33 rows=2 width=13)
                      ->  Partial HashAggregate  (cost=3634.71..3634.75 rows=4 width=12)
                           Group Key: p.color_id
                           ->  Parallel Seq Scan on t_product p  (cost=0.00..3046.47 rows=117647 width=4)
                      ->  Index Scan using t_color_pkey on t_color c2  (cost=0.13..0.15 rows=1 width=9)
                           Index Cond: (color_id = p.color_id)
                 ->  Materialize  (cost=0.00..1.06 rows=4 width=5)
                      ->  Seq Scan on t_category c1  (cost=0.00..1.04 rows=4 width=5)
(15 rows)

新執行計劃的核心邏輯是直接讀取 product 主表,先按相關 ID 字段完成聚合計算,隨後再通過嵌套循環方式完成數據關聯。此後執行過程將變得非常高效,因為在 HashAggregate 之後,數據量已經被大幅壓縮,只剩下極少量行。這種方案的巧妙之處在於:在按 ID 完成聚合之後,只需要查找極少量名稱值,從而節省了大量重複迭代操作。

數據庫性能分析

從執行效率來看,新執行方式具備明顯優勢,性能對比如下所示:

old method:    95.3 ms
    new method:    16.8 ms

測試結果顯示,新方式的查詢速度提升 5 倍以上。並且隨着參與關聯的查找表數量增加,性能收益還將進一步放大,該優化在複雜報表、統計分析類場景中表現尤為突出。

補充説明:本次測試為首次運行,未啓用提示位(hint bits),採用全新統計信息;測試環境為 MacBook M3,數據庫配置為 PostgreSQL 默認參數。

CUBE:侷限性

儘管 PostgreSQL 19 的新優化機制在絕大多數場景下效果顯著,但仍然存在少數特性無法完全受益,GROUP BY CUBE 就是典型案例:

PgSQL
explain
SELECT    category_name, color_name, count(*)
FROM    t_product AS p, t_category AS c1, t_color AS c2
WHERE    p.color_id = c2.color_id
    AND c1.category_id = c1.category_id
GROUP BY CUBE(1, 2);

其對應的執行計劃如下:

                                          QUERY PLAN
----------------------------------------------------------------------------------------
 MixedAggregate  (cost=2.49..29372.74 rows=25 width=18)
   Hash Key: c1.category_name, c2.color_name
   Hash Key: c1.category_name
   Hash Key: c2.color_name
   Group Key: ()
   ->  Hash Join  (cost=2.49..13372.49 rows=800000 width=10)
         Hash Cond: (p.color_id = c2.color_id)
         ->  Seq Scan on t_product p  (cost=0.00..3870.00 rows=200000 width=4)
         ->  Hash  (cost=2.29..2.29 rows=16 width=14)
               ->  Nested Loop  (cost=0.00..2.29 rows=16 width=14)
                     ->  Seq Scan on t_category c1  (cost=0.00..1.04 rows=4 width=5)
                     ->  Materialize  (cost=0.00..1.06 rows=4 width=9)
                           ->  Seq Scan on t_color c2  (cost=0.00..1.04 rows=4 width=9)
(13 rows)

在該場景中可以看到,CUBE 所涉及的多組聚合仍然需要在上層統一完成。由於執行語義上的限制,相關聚合邏輯無法完全下推。需要指出的是,與常規 GROUP BY 相比,CUBE 在實際業務系統中的使用頻率相對較低,因此對整體優化收益影響有限。

結語

若需進一步瞭解 PostgreSQL 中的 CUBE 與分組集(Grouping Sets)相關機制,可參考以下技術資料:

  • PostgreSQL grouping sets:ROLLUP & CUBE
  • Citus:7 個常用高級 SQL 工具

原文鏈接:

https://www.cybertec-postgresql.com/en/super-fast-aggregation...

作者:Hans-Jürgen Schönig

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.