把 PostgreSQL 分区裁剪查清楚:enable_partition_pruning 不只是一个开关

2026-07-06 30 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

PostgreSQL 的 enable_partition_pruning 看起来像一个普通优化器开关,但它影响的是分区表查询里非常关键的一步:把明显不需要访问的分区从扫描计划中剔除。容易踩坑的地方在于,分区裁剪不只发生在生成执行计划时,也可能发生在执行过程中。排查慢查询时,只看一眼 EXPLAIN 里的静态计划,往往不够。

分区裁剪到底省了什么

分区表的代价来自“可能要看很多张子表”。如果查询条件能明确落在某个分区范围内,数据库就没有必要扫描其他分区。

例如按月分区的订单表:

SELECT *
FROM orders
WHERE order_date >= DATE '2025-01-01'
  AND order_date <  DATE '2025-02-01';

如果 ordersorder_date 做范围分区,理想情况下 PostgreSQL 只访问 2025 年 1 月对应的分区,而不是把全年甚至多年的分区都扫一遍。

enable_partition_pruning 控制的就是这类优化是否启用。通常它应该保持开启,因为关闭它会让 PostgreSQL 更倾向于保留不必要的分区扫描,查询计划会变重。

两个阶段:计划时裁剪和执行时裁剪

这篇内容里最值得记住的一点是:PostgreSQL 的 partition pruning 有两个阶段。

计划时裁剪发生在生成查询计划时。条件里如果有常量,优化器可以直接判断哪些分区不可能匹配,然后把它们排除掉。

EXPLAIN
SELECT *
FROM orders
WHERE order_date = DATE '2025-01-15';

这里的日期是常量,优化器在计划阶段就有机会知道只需要访问哪个分区。

执行时裁剪发生在查询真正运行时。典型场景是查询条件依赖参数、子查询结果或运行期才能确定的值。计划阶段 PostgreSQL 还不知道具体值,因此计划里可能看起来保留了多个分区;到了执行阶段,它再根据真实参数裁掉不需要的分区。

这也是为什么排查时要看 EXPLAIN (ANALYZE),而不只是 EXPLAIN。前者能告诉你执行过程中实际发生了什么。

可以这样实践:用 psql 观察裁剪效果

下面是一段可以直接在测试库里运行的 SQL。它创建一个按日期范围分区的表,然后分别观察开启和关闭 enable_partition_pruning 时的计划差异。

运行前请确认你连接的是测试数据库,因为脚本会删除并重建 demo_orders 表。

DROP TABLE IF EXISTS demo_orders CASCADE;

CREATE TABLE demo_orders (
    id bigint GENERATED ALWAYS AS IDENTITY,
    order_date date NOT NULL,
    amount numeric(12, 2) NOT NULL
) PARTITION BY RANGE (order_date);

CREATE TABLE demo_orders_2025_01 PARTITION OF demo_orders
    FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');

CREATE TABLE demo_orders_2025_02 PARTITION OF demo_orders
    FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');

CREATE TABLE demo_orders_2025_03 PARTITION OF demo_orders
    FOR VALUES FROM ('2025-03-01') TO ('2025-04-01');

INSERT INTO demo_orders (order_date, amount)
SELECT d::date, (random() * 1000)::numeric(12, 2)
FROM generate_series('2025-01-01'::date, '2025-03-31'::date, interval '1 day') AS d;

ANALYZE demo_orders;

SET enable_partition_pruning = on;

EXPLAIN (ANALYZE, COSTS OFF)
SELECT *
FROM demo_orders
WHERE order_date >= DATE '2025-02-10'
  AND order_date <  DATE '2025-02-11';

SET enable_partition_pruning = off;

EXPLAIN (ANALYZE, COSTS OFF)
SELECT *
FROM demo_orders
WHERE order_date >= DATE '2025-02-10'
  AND order_date <  DATE '2025-02-11';

RESET enable_partition_pruning;

你要看的不是某个固定的输出文本,而是扫描节点里出现了多少个分区。开启裁剪时,查询应该只需要访问 2 月分区;关闭后,计划通常会保留更多分区扫描。

如果你想从命令行快速跑一次,可以这样:

psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f partition_pruning_demo.sql

DATABASE_URL 换成你的测试库连接串,或者直接使用 psql -d your_db_name

排查慢查询时别只盯着一个开关

enable_partition_pruning 是入口,但不是全部。实践里更常见的问题是:查询条件写法让优化器难以判断分区边界。

比较稳妥的写法是让分区键直接出现在谓词里:

-- 更容易被分区裁剪利用
SELECT *
FROM demo_orders
WHERE order_date >= DATE '2025-02-01'
  AND order_date <  DATE '2025-03-01';

需要谨慎的是把函数套在分区键上:

-- 可以这样作为反例检查:这种写法可能让优化器更难利用分区边界
SELECT *
FROM demo_orders
WHERE date_trunc('month', order_date) = TIMESTAMP '2025-02-01';

这并不是说所有函数表达式都会彻底破坏优化,而是说排查分区裁剪时,应该优先确认谓词是否能清楚表达分区范围。生产 SQL 里越多隐式类型转换、函数包装、复杂表达式,越需要用 EXPLAIN (ANALYZE) 验证真实行为。

采用建议:把验证放进发布流程

对分区表做性能优化时,可以按这个清单走:

  • 确认 enable_partition_pruning 没被会话、角色或数据库级配置意外关闭。
  • 对关键查询同时看 EXPLAINEXPLAIN (ANALYZE),区分计划时裁剪和执行时裁剪。
  • 谓词尽量直接使用分区键,避免不必要的函数包装和类型绕路。
  • 参数化 SQL、预编译语句、ORM 生成 SQL 都要实际解释计划,不要只看手写 SQL。
  • 分区数量很多时,把“扫描了哪些分区”作为性能回归检查的一部分。

enable_partition_pruning 的价值不在于让你手动调一个神秘参数,而在于提醒你:分区表的性能要靠可验证的裁剪行为支撑。看清楚计划阶段和执行阶段分别裁掉了什么,才知道查询是在利用分区,还是只是把一张大表拆成了很多小麻烦。


相关推荐