postgresql 16 的 refresh materialized view 仅支持全量刷新,无内核级增量能力;concurrently 需唯一索引且依赖首次完整刷新作为比对基准,优化查询定义比调整刷新方式更有效。

REFRESH MATERIALIZED VIEW 在 PostgreSQL 16 中仍是全量执行,没有内核级增量刷新能力。想靠它自动跳过未变数据、只更新新增/修改行?做不到。
REFRESH MATERIALIZED VIEW 默认就是全量重算
每次执行 REFRESH MATERIALIZED VIEW my_mv,PostgreSQL 都会丢弃旧数据,重新运行原始 SELECT 查询,生成全新结果集。它不看源表哪些行变了,也不依赖 WAL 或触发器捕获变更。
- 即使源表只新增 1 行,也会扫描全部基表、重跑聚合、重建整个物化视图
-
WITH NO DATA仅清空内容,不删结构;后续仍需完整刷新才能恢复可查状态 - 若定义含
ORDER BY,刷新后顺序才稳定;否则顺序由执行计划决定,不可依赖
CONCURRENTLY 不等于“增量”,只是避免读锁
加 CONCURRENTLY 后确实允许其他会话 SELECT 物化视图,但底层仍是全量查询 + 差集比对,不是增量逻辑。
- 必须提前在物化视图上建好
UNIQUE或PRIMARY KEY索引,且所有索引列NOT NULL - 索引列顺序要和
GROUP BY列完全一致;(a, b)索引不能用于GROUP BY b, a - 若物化视图刚创建且未填充过(即处于
WITH NO DATA状态),CONCURRENTLY会直接拒绝执行 - 常见报错固定为:
ERROR: cannot refresh materialized view "xxx" concurrently because it does not have a unique index
真正能提速的,往往在 SQL 定义里
刷新耗时主要卡在原始查询本身。优化物化视图定义,比折腾刷新参数更有效。
- 避免
DISTINCT ON、窗口函数、复杂子查询——它们会让CONCURRENTLY失效,甚至退化为全量扫描比对 - 聚合字段类型要和
WHERE条件严格一致:比如EXTRACT(YEAR FROM ts)返回double precision,就不能写WHERE year = 2023(整型),否则隐式转换废掉索引 - 日增数据量若稳定且 CONCURRENTLY 可接受;否则宁可低峰期用非并发刷新,也别高频硬扛
- 实测 400 万行:非并发刷新 32 秒,
CONCURRENTLY耗时 147 秒,且pg_stat_activity显示大量Hash Join和Sort
所谓“增量刷新”只能靠外部手段模拟
PostgreSQL 内核不提供基于日志或变更捕获的增量机制。任何“只刷新数据”的方案,都得自己控制范围。
- 用
pg_matview扩展:本质是把物化视图当普通表 + 触发器记录变更 + 手写INSERT ON CONFLICT逻辑,不自动推导变化行 - 手动维护位点:比如存最新
updated_at到matview_refresh_state表,刷新 SQL 中加WHERE updated_at > last_updated - 前提必须有可靠单调递增字段:自增 ID、严格递增时间戳;若
updated_at可能回退或重复,就无法准确界定边界
最容易被忽略的是:哪怕索引建对了、字段非空、列全覆盖,只要物化视图没经历过一次完整刷新(WITH DATA),CONCURRENTLY 就永远执行不了——它需要一个“旧版本”做比对基准。










