PostgreSQL 15中物化视图并发刷新必须显式创建覆盖全部GROUP BY列且全为NOT NULL的唯一索引,否则REFRESH CONCURRENTLY直接报错;索引顺序须与GROUP BY一致,类型需严格对齐,且必须先用WITH DATA初始化,不可为WITH NO DATA。

PostgreSQL 15 中创建物化视图必须显式指定唯一索引,否则无法安全刷新——这不是可选项,而是并发刷新(REFRESH MATERIALIZED VIEW CONCURRENTLY)的硬性前提。
CREATE MATERIALIZED VIEW 必须带 WITH DATA 且 GROUP BY 列需业务唯一
PostgreSQL 15 不允许用 WITH NO DATA 创建后直接并发刷新;更关键的是,如果 SELECT 中的 GROUP BY 字段组合在业务逻辑中不构成唯一性(比如仅按 department 分组,但一个部门每年有多条记录),后续建唯一索引会失败,导致 CONCURRENTLY 刷新报错 ERROR: could not create unique index。
-
WITH DATA是默认行为,可省略,但不能写成WITH NO DATA—— 否则REFRESH ... CONCURRENTLY直接报错“materialized view must contain data” - 确保
GROUP BY列能天然构成唯一键:例如department, EXTRACT(YEAR FROM order_date)在业务中不会出现重复组合;若不确定,宁可用ROW_NUMBER() OVER (PARTITION BY department, year ORDER BY MAX(order_date))生成稳定序号,再对这列建唯一索引(但要避免RANDOM()或未排序的ORDER BY引入非确定性) -
EXTRACT(YEAR FROM ...)返回double precision,和整型比较时可能触发隐式转换,废掉索引——改用date_part('year', ...)::int或直接order_date::date - INTERVAL '1 day' * (EXTRACT(DOY FROM order_date) - 1)截出年份日期
CREATE UNIQUE INDEX 必须覆盖全部 GROUP BY 列且 NOT NULL
索引字段顺序必须与 GROUP BY 子句完全一致,且所有列都得是 NOT NULL。PostgreSQL 15 对 CONCURRENTLY 的校验比旧版更严格:哪怕一个字段允许 NULL,或索引是 (year, department) 而 GROUP BY 是 department, year,都会回退到全表扫描比对,刷新变慢十倍以上。
- 建索引前先确认字段可空性:
SELECT column_name, is_nullable FROM information_schema.columns WHERE table_name = 'sales_summary';,对 NULL 列用COALESCE(..., -1)或WHERE ... IS NOT NULL过滤 - 唯一索引必须显式创建,不能依赖主键(物化视图没主键概念),命令形如:
CREATE UNIQUE INDEX idx_sales_summary_dept_year ON sales_summary (department, year); - 索引名无特殊要求,但建议带表名前缀,避免迁移或清理时误删
REFRESH MATERIALIZED VIEW CONCURRENTLY 的实际开销远高于直觉
它不锁表,但底层是逐行比对新旧数据 + 用临时表 merge,I/O 和 CPU 消耗显著高于全量刷新。当物化视图日增数据量超过总量 5%,或 ANALYZE 统计信息过期时,CONCURRENTLY 可能从 2 秒飙升至 40 秒,并在 pg_stat_activity 中大量出现 Hash Join 和 Sort 状态。
- 刷新前务必执行:
ANALYZE sales_summary;,否则优化器选错执行计划 - 若日增量稳定且小(如每天新增 1 万行,总数据 200 万行),
CONCURRENTLY可接受;否则改用非并发刷新 + 定时任务(如pg_cron在凌晨 2 点跑REFRESH MATERIALIZED VIEW sales_summary;) -
CONCURRENTLY不能在事务块里执行,也不能和WITH NO DATA共存——这两个限制 PostgreSQL 15 没有放宽
聚合字段类型必须和源表严格对齐
类型不一致会导致查询时无法走索引,甚至隐式转换让整个物化视图“失效”。常见坑点是 SUM(amount) 源表为 numeric,但物化视图里因表达式写法变成 double precision,后续 WHERE sum_amount > 1000 就没法用索引。
- 显式声明类型:
SUM(amount)::numeric(18,2),而非依赖推导 -
COUNT(*)返回int8,但若和int4字段 JOIN,可能触发 cast;统一用COUNT(*)::int4避免 - NULL 处理要显式:
COALESCE(SUM(amount), 0),否则物化视图里该列为NULL,而源表对应聚合结果是非 NULL,JOIN 时匹配失败
真正麻烦的从来不是建视图那条 SQL,而是 GROUP BY 列是否真唯一、索引字段是否全 NOT NULL、类型是否一丝不差——这三个点漏掉任何一个,物化视图就从性能加速器变成定时阻塞器。










