PostgreSQL 分区表排序优化:Append Sort 优化为 Merge Append

最近在优化一个 PostgreSQL 查询时,遇到一个典型的 分区表排序性能问题,通过索引重构和执行计划分析,,优化效果非常明显,记录一下完整过程。


一、问题背景

业务中有一张 按状态分区的表

复制代码
order_partitioned_table
├── partition_active      (status = 0)
└── partition_archive     (default)

查询 SQL 如下:

复制代码
SELECT user_id, object_id
FROM order_partitioned_table
WHERE status IN (0, 22)
  AND expire_time < ?
ORDER BY expire_time ASC, object_id ASC;

该查询返回 60W+ 行数据,执行计划如下:

复制代码
Append
  -> Index Scan partition_active
  -> Index Scan partition_archive
Sort

执行时间:

复制代码
Execution Time: 53s

明显瓶颈在:

复制代码
Append + Sort

二、为什么会慢?

执行流程是:

  1. 扫描 partition_active

  2. 扫描 partition_archive

  3. 拼接结果 (Append)

  4. 对 60W 行整体排序 (Sort)

也就是:

复制代码
Append
   ↓
Sort (600k rows)

这种模式的问题:

  • 排序数据量大

  • CPU 消耗高

  • 内存消耗大

  • 可能发生磁盘排序


三、理想执行计划

我们希望变成:

复制代码
Merge Append

而不是:

复制代码
Append + Sort

为什么?

Append + Sort

相当于:

  1. 两堆乱序数据合并

  2. 再整体排序

复杂度:

复制代码
O(N log N)

Merge Append

前提:

两个子查询 已经有序

然后:

复制代码
归并排序

复杂度:

复制代码
O(N)

这就是优化核心。


四、优化思路

关键点:

每个分区本身就按 ORDER BY 顺序输出

ORDER BY:

复制代码
expire_time, object_id

因此索引必须:

复制代码
(expire_time, object_id)

并且:

复制代码
WHERE status IN (0,22)

因此采用 Partial Index


五、最终优化索引

Active 分区

复制代码
CREATE INDEX CONCURRENTLY partition_active_exp_idx
ON partition_active (expire_time, object_id, user_id)
WHERE status IN (0, 22);

Archive 分区

复制代码
CREATE INDEX CONCURRENTLY partition_archive_exp_idx
ON partition_archive (expire_time, object_id, user_id)
WHERE status IN (0, 22);

六、优化后的执行计划

复制代码
Merge Append
   -> Index Only Scan partition_active
   -> Index Only Scan partition_archive

执行时间:

复制代码
Execution Time: 559 ms

优化效果:

阶段 执行时间
初始 53s
优化后 0.55s

提升:

复制代码
≈ 100x 性能提升

七、为什么 Partial Index 更适合

原索引:

复制代码
(status, user_id, expire_time, ...)

问题:

  • status 放在前面破坏排序

  • 无法使用 Merge Append

而 Partial Index:

复制代码
(expire_time, object_id)
WHERE status IN (0,22)

优势:

  • 索引更小

  • 顺序更符合 ORDER BY

  • 更容易触发 Merge Append


八、Merge Append vs Append + Sort

Append + Sort

流程:

复制代码
partition_active
partition_archive
      ↓
    Append
      ↓
     Sort

需要:

  • 全量排序

  • 大内存

  • CPU 高


Merge Append

流程:

复制代码
partition_active (ordered)
partition_archive (ordered)
      ↓
   Merge Append

优势:

  • 不需要整体排序

  • 只做归并

  • 更快更稳定


九、PostgreSQL 版本注意事项

该优化在 PostgreSQL 14 测试通过。

PostgreSQL 14 对分区优化能力相比 15/16 略弱,因此:

  • 索引顺序尤为重要

  • Partial Index 更容易触发 Merge Append

在 PostgreSQL 15+ 中,该优化效果通常更明显。


十、总结

本次优化关键点:

  1. 避免 Append + Sort

  2. 让每个分区按排序键输出

  3. 使用 Partial Index

  4. 触发 Merge Append

最终实现:

复制代码
53s → 559ms

分区表排序优化,核心就是:

让每个分区先排好,再合并,而不是先合并再排序


如果你也遇到 PostgreSQL 分区表排序慢的问题,这种思路通常非常有效。


相关推荐
ClouGence14 小时前
Oracle 数据同步为什么会出现数据不一致?长事务是常被忽略的原因
数据库·后端·oracle
飞将16 小时前
从零实现数据库(2)——HashIndex + IndexManager
数据库
Nturmoils2 天前
订单列表慢查询,先看 WHERE、ORDER BY 和 LIMIT
数据库
渣波2 天前
拒绝 SQL 焦虑!手把手带你用 NestJS + Prisma + DTO 写出“防弹”级后端代码
javascript·数据库·后端
大大大大晴天2 天前
Hudi技术内幕:RecordPayload到RecordMerger
大数据
SelectDB2 天前
秒级弹性、最高降本 70%:SelectDB Serverless 如何重塑云数仓资源效率
大数据·后端·云原生
WhoAmI2 天前
MapReduce框架原理解析一:InputFormat
大数据·hadoop
WhoAmI2 天前
MapReduce框架原理解析三:OutputFormat
大数据·hadoop
WhoAmI2 天前
MapReduce框架原理解析二:Shuffle
大数据·hadoop
倔强的石头_3 天前
KingbaseES 新版MySQL 兼容版体验:旧版迁移 + 功能实测
数据库