最近在优化一个 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 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+ 中,该优化效果通常更明显。
十、总结
本次优化关键点:
-
避免
Append + Sort -
让每个分区按排序键输出
-
使用 Partial Index
-
触发 Merge Append
最终实现:
53s → 559ms
分区表排序优化,核心就是:
让每个分区先排好,再合并,而不是先合并再排序
如果你也遇到 PostgreSQL 分区表排序慢的问题,这种思路通常非常有效。