物化视图语法只读但不防写穿透,需权限隔离;并发刷新必须建唯一索引且字段顺序、非空性、类型须与GROUP BY严格一致;只读角色须设NOLOGIN、default_transaction_read_only及全链路SELECT权限。

物化视图天生只读,但默认不防写穿透
PostgreSQL 的 MATERIALIZED VIEW 本身不支持 INSERT/UPDATE/DELETE,语法上就是只读的——你执行 INSERT INTO mv_name 会直接报错 cannot insert into materialized view。但这不等于“安全只读”:用户仍可通过底层表、函数、临时表或未授权的 schema 操作绕过。真正要阻断写路径,得靠权限层隔离,不是靠物化视图语法。
创建时必须显式加唯一索引才能并发刷新
如果你打算用 REFRESH MATERIALIZED VIEW CONCURRENTLY(生产环境几乎必须),那创建后立刻建唯一索引是硬性前提,否则刷新会失败并提示 there is no unique index on the materialized view。
-
GROUP BY字段必须能构成业务唯一性,例如(region, sale_date);若不能保证,可加ROW_NUMBER() OVER (PARTITION BY region, sale_date ORDER BY region)并在索引中包含该列 - 索引字段顺序必须和
GROUP BY完全一致,比如GROUP BY department, year就得建CREATE UNIQUE INDEX ON mv_name (department, year),反过来就无效 - 被索引的列不能为
NULL,否则CONCURRENTLY刷新时会跳过这些行,导致数据不一致
权限控制必须覆盖角色、schema 和默认权限
只对物化视图 GRANT SELECT 是没用的。用户只要对底层表有 SELECT 权限,就能绕过物化视图直接查原表;如果还意外拿到 INSERT 权限,甚至能改源数据破坏一致性。
- 创建无登录能力的角色:
CREATE ROLE readonly_role WITH NOLOGIN; - 强制会话只读:
ALTER ROLE readonly_role SET default_transaction_read_only = on; - 只给 schema 使用权:
GRANT USAGE ON SCHEMA public TO readonly_role; - 只授物化视图和基表的
SELECT:GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role;(含物化视图) - 覆盖未来新建对象:
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_role;
别把物化视图当普通表用 JOIN 或 WHERE 过滤
物化视图查询变慢,90% 不是它没生效,而是类型隐式转换或 NULL 处理废掉了索引。比如 EXTRACT(YEAR FROM order_date) 返回 double precision,而你物化视图里存的是 INT,JOIN 时就会触发类型转换,索引失效。
- 聚合字段类型必须和源表严格对齐,避免
COUNT(*)后再::BIGINT这类显式转换 - WHERE 中过滤字段,优先用物化视图里已计算好的列,而不是回推到原始表达式(如用
sale_year而非EXTRACT(YEAR FROM order_date)) - 如果物化视图含
NULL值,且业务逻辑依赖非空判断,务必在定义时用COALESCE(sale_date, '1970-01-01'::DATE)显式处理,否则索引可能无法命中
default_transaction_read_only 的角色,随时可能被应用连接误写。

















