不能直接用 UNION ALL 拼接热表和冷表,因为会导致全分区扫描、无法下推条件、主键冲突及分区裁剪失效;需确保索引、类型一致、显式范围过滤并避免视图内动态函数。

为什么不能直接用 UNION ALL 拼接热表和冷表?
因为冷表(比如按月分区的 orders_202301、orders_202302)通常没有主键或唯一约束,而热表(orders_current)可能有 PRIMARY KEY 或 ON CONFLICT 逻辑。直接 UNION ALL 后再 ORDER BY created_at LIMIT 100,PostgreSQL 会强制扫描全部冷表分区——哪怕你只想要最近 7 天的数据。性能崩得很快。
实操建议:
- 冷表必须带
created_at字段且有 B-tree 索引(即使只是局部索引),否则分区裁剪失效 - 热表和冷表的列名、数据类型必须严格一致,
NULL和''的语义也要对齐,否则视图查询时可能隐式转换失败 - 避免在视图里写
WHERE created_at > '2024-01-01'—— 这会让 PostgreSQL 无法下推条件到冷表分区,改用函数封装过滤逻辑
如何让视图自动路由到对应冷表分区?
PostgreSQL 本身不支持视图级的动态分区路由,但可以用 CREATE OR REPLACE VIEW 配合 TABLE + SELECT 显式声明每个冷表,并靠 CASE 或函数控制可见性。更可靠的做法是用 pg_pathman 或原生声明式分区(PostgreSQL 10+),然后让视图基于父表查询。
实操建议:
- 如果冷表已按
created_at做范围分区(如PARTITION BY RANGE (created_at)),直接在视图中SELECT * FROM orders_partitioned即可触发自动分区裁剪 - 若冷表是手动命名的独立表(如
orders_archive_2022),需用UNION ALL显式列出,但每条子句必须带WHERE created_at >= '2022-01-01' AND created_at ,且该范围要和表名实际覆盖时间严格匹配 - 别忘了给每个冷表的
created_at字段建索引:CREATE INDEX ON orders_archive_2022 (created_at)
VIEW 中怎么处理热表与冷表的主键冲突?
热表可能有自增 id,冷表从旧系统导出后 id 可能重叠。直接 UNION ALL 会导致去重困难,DISTINCT 代价极高,GROUP BY 又容易丢失字段。
实操建议:
- 在视图中统一加来源标识字段:
'current'::text AS source和'archive_2022'::text AS source,后续应用层可据此判断数据归属 - 如需全局唯一逻辑 ID,不要依赖原始
id,改用组合键:source || '_' || id AS logical_id,注意source值必须固定、无空格、无特殊字符 - 避免在视图里做
ROW_NUMBER() OVER (ORDER BY created_at DESC)—— 窗口函数会在合并结果集后执行,冷表全扫一遍才排序,极其慢
查询时为什么 EXPLAIN 显示没走分区裁剪?
常见错误是视图定义里用了 now()、current_date 或参数化变量(如 $1),导致 PostgreSQL 无法在计划阶段确定分区边界,于是退化为全表扫描。
实操建议:
- 把时间过滤条件从视图定义里剥离,放到外部查询中:
SELECT * FROM orders_combined_view WHERE created_at >= '2024-05-01' - 如果必须参数化,改用
PREPARE+EXECUTE,或用物化视图定期刷新(适合 T+1 场景) - 检查
pg_stats是否更新:ANALYZE orders_partitioned,否则优化器可能误判行数,跳过分区裁剪
分区裁剪不是银弹,它依赖准确的统计信息、显式的范围条件和一致的索引策略。漏掉其中一环,视图就退化成“看起来优雅、跑起来窒息”的样子。










