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二、为什么会慢执行流程是扫描 partition_active扫描 partition_archive拼接结果 (Append)对 60W 行整体排序 (Sort)也就是Append ↓ Sort (600k rows)这种模式的问题排序数据量大CPU 消耗高内存消耗大可能发生磁盘排序三、理想执行计划我们希望变成Merge Append而不是Append Sort为什么Append Sort相当于两堆乱序数据合并再整体排序复杂度O(N log N)Merge Append前提两个子查询已经有序然后归并排序复杂度O(N)这就是优化核心。四、优化思路关键点让每个分区本身就按 ORDER BY 顺序输出ORDER BYexpire_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 SortAppend 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 中该优化效果通常更明显。十、总结本次优化关键点避免Append Sort让每个分区按排序键输出使用 Partial Index触发 Merge Append最终实现53s → 559ms分区表排序优化核心就是让每个分区先排好再合并而不是先合并再排序如果你也遇到 PostgreSQL 分区表排序慢的问题这种思路通常非常有效。