PostgreSQL原生不支持增量刷新,REFRESH MATERIALIZED VIEW始终全量重算;CONCURRENTLY仅避免读锁但仍是全量执行+比对,真正增量需依赖pg_matview等扩展或手动维护位点+upsert逻辑。

REFRESH MATERIALIZED VIEW 是全量重算,不是增量更新,每次执行都会丢弃旧数据、重新跑一遍原始 SELECT 查询。
REFRESH MATERIALIZED VIEW 基本用法与锁行为
不带 CONCURRENTLY 的刷新会获取排他锁(ACCESS EXCLUSIVE),期间其他查询无法读取该物化视图;加了 CONCURRENTLY 则只加共享锁,允许并发读,但要求物化视图上存在唯一索引(如 CREATE UNIQUE INDEX ON my_mv (id))。
-
REFRESH MATERIALIZED VIEW my_mv:默认带WITH DATA,刷新后可立即查询 -
REFRESH MATERIALIZED VIEW my_mv WITH NO DATA:清空内容,状态变为不可扫描,需后续再带数据刷新才能查 - 刷新失败时,物化视图保持原状(不会残留半新半旧数据)
- 如果原始定义含
ORDER BY,刷新结果才保证有序;否则顺序不确定
REFRESH MATERIALIZED VIEW CONCURRENTLY 的限制条件
这个选项看似友好,但容易因索引缺失或结构变更直接报错,常见错误包括:
-
ERROR: cannot refresh materialized view "my_mv" concurrently because it does not have a unique index:必须提前建好覆盖所有行的唯一索引(主键或组合唯一键均可) - 若物化视图定义中用了
DISTINCT ON或窗口函数,即使有唯一索引也可能不支持CONCURRENTLY - 刷新过程仍会全量执行底层查询,只是比非并发模式多做一次“逐行比对 + 差异更新”,性能未必更快,且不减少 I/O 和 CPU 消耗
- 不能在事务块里使用
CONCURRENTLY版本
为什么没有真正的增量刷新?
PostgreSQL 15 及之前版本内核不提供基于日志或变更捕获的增量机制。所谓“增量”只能靠外部手段模拟:
-
pg_matview扩展本质是把物化视图变成一张普通表 + 触发器记录变更 + 用户手写INSERT ... ON CONFLICT逻辑,它不自动推导哪几行变了 - 手动实现需自己维护刷新位点(比如存进
matview_refresh_state表),并在刷新 SQL 中加WHERE updated_at > last_updated - 任何“增量”方案都绕不开源表要有可靠单调递增字段(如自增 ID、严格递增时间戳),否则无法准确界定变化边界
- 别指望
REFRESH MATERIALIZED VIEW CONCURRENTLY是增量——它只是避免锁表,背后仍是全量 SELECT + 全量比对
定时刷新的实用做法(不用 pg_cron 也能跑)
最轻量的方式是在 shell 里用 psql 配合 \watch,适合开发/测试环境:
psql -c "REFRESH MATERIALIZED VIEW CONCURRENTLY sales_summary;" \watch 300
生产环境建议用 pg_cron 扩展(需启用并配置 shared_preload_libraries):
SELECT cron.schedule('refresh-sales-summary', '0 * * * *', $$REFRESH MATERIALIZED VIEW CONCURRENTLY sales_summary$$);- 注意:调度任务失败不会自动重试,需查
cron.job_run_details表确认状态 - 若刷新耗时波动大,避免固定高频调度(如每分钟),防止任务堆积
真正难的从来不是怎么调用 REFRESH MATERIALIZED VIEW,而是判断“什么时候该刷”和“刷完怎么验证数据一致性”——这两点几乎从不写在文档里,却决定线上是否出问题。










