PostgreSQL视图默认可更新,仅CREATE VIEW+GRANT SELECT无法保证只读;必须结合角色权限收窄、SECURITY DEFINER函数封装、default_transaction_read_only设置及全面REVOKE DML/DDL权限。

PostgreSQL 里仅靠 CREATE VIEW + GRANT SELECT 无法保证视图只读——用户仍可能通过视图底层表、函数或临时对象执行 DML。 真正的只读视图必须配合角色权限收窄、默认权限控制与运行时隔离机制,否则形同虚设。
为什么 CREATE VIEW 本身不防 DML
PostgreSQL 的视图默认是“可更新”的(updatable),只要满足特定条件(如单表、无聚合、无 DISTINCT、主键列完整暴露等),INSERT/UPDATE/DELETE 就能穿透视图直接改底层表。即使你没给用户底层表权限,只要该用户对视图有 INSERT 权限且视图可更新,就可能触发隐式写入。
- 视图可更新性由
pg_class.relkind = 'v'和pg_views.is_updatable决定,不是语法开关 -
GRANT SELECT ON VIEW v TO u;不自动拒绝INSERT INTO v—— PostgreSQL 默认允许尝试,失败才报错 - 若用户同时拥有底层表
SELECT权限,INSERT INTO v可能静默成功
用 SECURITY DEFINER 函数封装视图查询
绕过视图可更新风险最稳妥的方式:不暴露视图本身,而是创建一个 SECURITY DEFINER 函数返回结果集,调用者只能 SELECT * FROM fn_readonly_report(),无法对函数名执行 DML。
- 函数定义者必须是只具备
SELECT权限的专用角色(如readonly_role) - 函数内用
RETURN QUERY SELECT ... FROM real_table,不暴露表名 - 显式禁止调用者对函数执行
INSERT/UPDATE/DELETE:REVOKE INSERT, UPDATE, DELETE ON FUNCTION fn_readonly_report() FROM PUBLIC; - 函数体中避免引用用户无权访问的对象(如系统表、其他 schema 表),否则会因权限不足而报错
撤销 DML 权限并阻断所有写入口
仅限制视图或函数不够,必须从角色层面切断所有 DML 和 DDL 路径。PostgreSQL 没有“只读角色”内置概念,必须手动逐层 DENY(实际用 REVOKE + 权限剥夺):
- 先创建无登录能力的基础角色:
CREATE ROLE readonly_role WITH NOLOGIN; - 禁用连接后端写能力:
ALTER ROLE readonly_role SET default_transaction_read_only = on; - 收回 schema 创建权:
REVOKE CREATE ON SCHEMA public FROM readonly_role; - 确保只授予
USAGE和SELECT:GRANT USAGE ON SCHEMA public TO readonly_role;+GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role; - 覆盖新增对象:
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_role; - 别漏掉序列和函数:
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO readonly_role;,但REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM readonly_role;(除非明确需要只读函数)
容易被忽略的逃逸点
很多 DBA 在测试时只验证了 SELECT 成功、UPDATE 报错,就认为完成了。但以下路径仍可能被利用:
-
CREATE TEMP TABLE+INSERT:用户可用临时表缓存数据并计算,虽不改业务表,但属资源滥用;用REVOKE TEMP ON DATABASE db_name FROM readonly_role;阻断 - 自定义函数调用:若函数标记为
SECURITY INVOKER且用户有底层表权限,仍可间接写入;一律用SECURITY DEFINER并由最小权限角色定义 - 物化视图刷新:
REFRESH MATERIALIZED VIEW是 DML 操作,需显式REVOKE该权限(如果未授则默认无) - 扩展插件功能:如
file_fdw或postgres_fdw可能绕过本地权限模型;生产环境禁用非必要扩展
真正安全的只读,不是“不让用户写”,而是“让用户没有可写的对象、上下文和能力”。每一步收窄都得验证,不能依赖某一层的“看起来只读”。

















