分区裁剪未生效是因为JOIN条件中分区键未显式参与过滤,仅靠ON o.order_id = oi.order_id无法触发裁剪;必须在WHERE或JOIN中显式添加分区键范围条件如o.order_date >= '2024-01-01'。

直接调大 work_mem 不解决分区表 JOIN 慢的问题——真正瓶颈往往在分区裁剪失效、连接顺序错乱或跨分区哈希溢出,而不是内存不够。
为什么 EXPLAIN 里看到 Hash Join 却没走分区裁剪?
分区裁剪(Partition Pruning)只对 WHERE 条件中直接使用分区键生效,JOIN 条件里的分区键默认不触发裁剪。比如:orders 按 order_date 分区,但 JOIN 是 ON o.order_id = oi.order_id,优化器根本不知道该扫哪个分区。
- 必须把分区键显式放进 JOIN 的过滤逻辑里,例如加
AND o.order_date >= '2024-01-01' AND o.order_date ,哪怕业务上已隐含此约束 - 如果 JOIN 表也做了相同分区(如
order_items按order_date分区),可尝试用JOIN ... ON ... AND o.order_date = oi.order_date,部分版本能触发双向裁剪 - 检查
pg_partitioned_table和pg_inherits确认子表relkind = 'r'且有正确pg_constraint,缺失 CHECK 约束会导致裁剪完全失效
分区表 JOIN 时 Hash Join 还写磁盘?先看是不是并行放大了内存需求
每个并行 worker 都会独立申请一份 work_mem,而分区表天然容易触发并行扫描——但 pg_stat_progress_hash 只反映主进程状态,容易误判。
- 执行
EXPLAIN (ANALYZE, BUFFERS),确认实际用了几个并行 worker(看Gather节点下的Workers Planned) - 若
max_parallel_workers_per_gather = 4,且你设了work_mem = '64MB',单个查询最多吃掉4 × 64MB = 256MB内存,远超预期 - 临时禁用并行验证:在会话里
SET max_parallel_workers_per_gather = 0,再跑一次 EXPLAIN,对比 Hash 节点是否还报writing to disk due to insufficient memory - 更稳妥的做法是:用
SET LOCAL work_mem = '256MB'配合SET LOCAL max_parallel_workers_per_gather = 2,避免全局震荡
JOIN 多个分区表时,连接顺序影响裁剪范围
PostgreSQL 默认按代价估算连接顺序,但代价模型常低估分区裁剪收益,导致先 JOIN 小表再过滤大分区表,结果扫全量子表。
- 用括号强制控制顺序:
FROM (orders PARTITIONED_BY_DATE) JOIN order_items ON ...不起作用;但FROM (orders WHERE order_date BETWEEN ...) JOIN order_items ON ...能确保先裁剪再 JOIN - 对关键查询,加
SET join_collapse_limit = 1禁用连接重排,让 SQL 书写顺序成为执行顺序(注意:仅限该会话) - 如果驱动表是分区表且条件强筛选(如
WHERE status = 'paid'),先建扩展统计:CREATE STATISTICS orders_status_date ON status, order_date FROM orders,再ANALYZE orders,帮优化器预估裁剪后行数
别忽略分区键类型和索引对 JOIN 性能的隐性拖累
分区键类型不一致或缺失索引,会让 JOIN 前的扫描变成 Seq Scan,再大的 work_mem 也救不了——因为哈希表输入数据本身就已经爆炸了。
- 检查
orders和order_items的 JOIN 字段类型是否完全一致(int4vsint8或textvsvarchar),不一致会触发隐式转换,使分区键上的索引失效 - 每个分区子表都要单独建索引:
CREATE INDEX CONCURRENTLY ON orders_202401 (order_id),父表上的索引不自动下推 - 若 JOIN 条件含函数(如
ON date_trunc('month', o.create_time) = date_trunc('month', oi.create_time)),分区裁剪彻底失效,改用生成列 + 索引
最常被跳过的动作:在 EXPLAIN 输出里盯住 Partition Filter 和 Actual Partitions 两行——如果它们显示 (0) 或空,说明裁剪根本没发生,所有后续优化都是徒劳。

















