普通视图存逻辑、物化视图存数据;前者实时查询基表、后者保存快照需手动刷新,二者语法相似但行为迥异,不可互换。

普通视图和物化视图根本不是同一类东西——一个存逻辑,一个存数据;选错直接导致查得慢、数据旧、锁表失败。
CREATE VIEW 和 CREATE MATERIALIZED VIEW 语法只差一个词,行为天壤之别
两者创建语句外观相似,但底层完全不兼容:
-
CREATE VIEW v_recent_orders AS SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days':不写入任何数据,只保存这行 SQL 文本;每次SELECT * FROM v_recent_orders都重跑整个查询,结果永远最新 -
CREATE MATERIALIZED VIEW mv_recent_orders AS SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days':创建时立刻执行查询,并把结果实实在在写进磁盘,变成一张带 OID 的物理表 - PostgreSQL 不允许用
CREATE MATERIALIZED VIEW语句创建普通视图,也不允许用CREATE VIEW创建物化视图——语法错误会直接报错,不是“功能降级”
REFRESH MATERIALIZED VIEW 会阻塞查询,除非你加 CONCURRENTLY
默认刷新方式是原子替换:先清空旧数据,再 INSERT 新结果。在此期间所有新查询都会等在锁上,直到刷新完成。
- 加
CONCURRENTLY可避免阻塞:REFRESH MATERIALIZED VIEW CONCURRENTLY mv_recent_orders - 但前提是该物化视图已有唯一索引(如主键或
UNIQUE列),否则报错:ERROR: cannot refresh materialized view "mv_recent_orders" concurrently - 没有唯一索引又想并发刷新?只能先建索引:
CREATE UNIQUE INDEX ON mv_recent_orders (id)(假设id是主键) - 注意:
CONCURRENTLY不支持全量刷新中的某些聚合函数(如array_agg),会触发隐式排序冲突
物化视图能建索引,普通视图不能
普通视图是纯 SQL 文本,数据库连它的“行”都看不到,自然无法建索引;物化视图是一张真实表,CREATE INDEX 完全可用。
- 例如:
CREATE INDEX idx_mv_region ON mv_recent_orders (region)能显著加速WHERE region = 'US'查询 - 但索引会拖慢
REFRESH:每刷新一次就要重建索引,如果每小时刷一次却只查两次,纯属浪费 I/O 和空间 - 普通视图的性能优化只能靠在基表上建索引——比如在
orders.created_at上建索引,才能让v_recent_orders查询变快
普通视图有时可写,物化视图一律只读
简单单表视图(无聚合、无 DISTINCT、SELECT 列含主键)支持直接 INSERT/UPDATE/DELETE,操作最终落在基表。
- 例如:
UPDATE active_employees SET salary = 9000 WHERE id = 1会更新employees表 - 物化视图不接受任何写操作:
INSERT INTO mv_recent_orders ...直接报错:ERROR: cannot insert into materialized view - 想“更新”物化视图?只有
REFRESH这一条路——它不是缓存,是快照副本,没提供中间态或 delta 同步机制
最易被忽略的点:物化视图的“过期”不是 bug,是设计特性。如果你需要毫秒级一致性,就别用它;如果你依赖 REFRESH 保障时效性,就得盯紧刷新任务是否真在跑——cron 或 pg_cron 挂了,没人会主动告诉你数据已经三天没动了。

















