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 分区表排序慢的问题,这种思路通常非常有效。


相关推荐
2603_965148115 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
ltl5 小时前
RocksDB 经典故障排查:L0、compaction 与 write stall
数据库
C++、Java和Python的菜鸟5 小时前
第2章 项目前置课-代码版本控制Git
大数据·elasticsearch·搜索引擎
Sammyyyyy6 小时前
如何在不停项目不停机的情况下切换AI大模型
大数据·人工智能
xfhuangfu6 小时前
Oracle中建立到CDB和PDB的连接
数据库·oracle·rpc
小小龙学IT6 小时前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
NineData6 小时前
DTCC 2026 预告|NineData CEO& 创始人叶正盛:面向 AI Agent 的数据库 DevOps 与数据复制实践
数据库·人工智能·数据库开发·devops·ninedata·数据库技术·dtcc
circuitsosk7 小时前
跨境电商智能化实战:AI如何赋能客服自动回复、广告智能投放与供应链预测
大数据·人工智能·python·langchain·智能客服
J_bean7 小时前
MySQL 事务是否必须手动开启?
数据库·mysql·数据库事务·自动提交·手动提交
J_bean7 小时前
MySQL InnoDB 如何检测死锁、判定死锁、处理死锁
数据库·mysql·死锁·死锁检测·处理死锁·死锁判定