CREATE VIEW 本身不提供只读保护,必须配合权限控制:先创建视图,再撤销用户对基表的DML权限,并仅授予视图SELECT权限;不同数据库实现方式不同,且需防范角色继承、隐式权限和应用层绕过。

CREATE VIEW 语句本身不带权限控制,只读性靠后续 GRANT 实现
视图(VIEW)在 SQL 中本质是保存的查询语句,它不存储数据,也不自带“只读”属性。即使你只写 SELECT,用户仍可能通过视图执行 INSERT、UPDATE 或 DELETE——前提是底层表权限允许,且数据库支持可更新视图(如 PostgreSQL 默认允许,MySQL 对简单单表视图也允许)。所以,仅靠 CREATE VIEW 无法防止篡改。
真正实现只读的关键步骤是:先建视图,再收回用户对基表的 DML 权限,并只授予对该视图的 SELECT 权限。
- PostgreSQL 示例:
CREATE VIEW user_summary AS SELECT id, name, created_at FROM users;
然后执行:REVOKE INSERT, UPDATE, DELETE ON users FROM app_user;
GRANT SELECT ON user_summary TO app_user;
- MySQL 不支持直接
REVOKE单表 DML 权限后保留视图访问,需用角色或显式GRANT SELECT ON view_name+REVOKE ALL PRIVILEGES ON table_name配合(注意:MySQL 8.0+ 支持角色,更清晰) - SQL Server 同理,
CREATE VIEW后必须用DENY INSERT, UPDATE, DELETE ON users TO [user]或通过角色隔离
避免创建可更新视图的常见陷阱
某些视图结构会让数据库自动认定为“可更新”,从而绕过你的只读意图。即使你没授 DML 权限,如果用户有基表权限,这些视图就可能被误用。
- 含
DISTINCT、GROUP BY、HAVING、聚合函数(如COUNT()、SUM())的视图,在几乎所有数据库中都不可更新 —— 这反而是安全的 - 但单表
SELECT *或SELECT col1, col2 FROM t视图,在 PostgreSQL 和 MySQL 中默认可更新,务必警惕 - 含子查询(尤其相关子查询)、
UNION、窗口函数(ROW_NUMBER()等)的视图,通常不可更新,适合只读场景 - Oracle 需显式加
WITH READ ONLY子句:CREATE VIEW emp_ro AS SELECT empno, ename FROM emp WITH READ ONLY;
否则仍可能被当作可更新视图处理(取决于 Oracle 版本与初始化参数)
权限链路必须断开:不能让视图用户间接持有基表权限
哪怕你给用户只授了视图的 SELECT,如果该用户属于某个拥有 users 表 UPDATE 权限的角色(比如 db_owner、schema_admin),那视图的只读约束就形同虚设。
- 检查用户实际权限:PostgreSQL 用
\z users或查pg_roles+pg_auth_members;SQL Server 用EXECUTE sp_helprolemember 'db_datawriter' - MySQL 8.0+ 推荐用角色管理:
CREATE ROLE readonly_role;
GRANT SELECT ON mydb.user_summary TO readonly_role;
REVOKE ALL PRIVILEGES ON mydb.users FROM readonly_role;
GRANT readonly_role TO app_user;
- 不要依赖“用户没主动
GRANT就安全”——继承自组/角色的隐式权限才是高频漏洞来源
应用层仍需校验:视图只防误操作,不防恶意绕过
视图和权限控制解决的是 SQL 层面的访问控制,但如果你的应用用同一数据库账号连接,又动态拼接 SQL,那攻击者仍可能绕过视图,直击基表(例如注入 ; DROP TABLE users; 或 SELECT * FROM users)。
- 生产环境应使用最小权限专用账号,该账号只被授予视图
SELECT,且明确REVOKE所有基表权限 - 避免应用以
root、sa、postgres等高权账号运行 - 视图名本身不是安全边界 —— 用户只要知道基表名,且权限未收敛,就能直接查;名字混淆(如叫
v_user_safe)毫无防护价值 - 审计日志里重点监控对基表的
UPDATE/DELETE操作,而非只盯视图访问
权限模型比视图定义复杂得多,最容易被忽略的是角色继承和跨 schema 引用——比如视图里 SELECT * FROM public.users,但用户仍有 other_schema.users 的权限,这种横向越权不会被视图本身挡住。

















