真正起作用的是权限回收动作而非视图本身;必须显式列字段、禁用SELECT*;需REVOKE基表权限并GRANT视图权限;避免可更新视图和语义化脱敏别名;视图定义不具物理隔离性。

仅建视图不撤基表权限,等于没做任何防护——真正起作用的是权限回收动作,不是视图本身。
CREATE VIEW 必须显式列出字段,禁用 SELECT *
视图定义里写 SELECT *,等于把基表所有列(包括后续新增的 internal_notes、salary_revision_date)全兜进来。哪怕你只查 name 和 email,元数据里照样暴露 password_hash 字段名和类型。
- 正确写法:
CREATE VIEW user_safe AS SELECT id, name, email, created_at FROM users; - MySQL 中若基表加了新列,
SELECT *视图会自动包含它;显式列表则完全免疫 - PostgreSQL 要求含表达式时必须声明列名,例如:
CREATE VIEW v AS SELECT email AS user_email FROM users;
REVOKE 和 GRANT 必须成对执行,缺一不可
只 GRANT SELECT ON user_safe TO app_user 不够,用户仍可用 SELECT ssn FROM users 直取。关键动作是砍掉基表权限链。
- 先执行:
REVOKE SELECT ON users FROM app_user;(PostgreSQL/MySQL 通用) - 再执行:
GRANT SELECT ON user_safe TO app_user; - PostgreSQL 额外要:
REVOKE USAGE ON SCHEMA public FROM app_user;,否则可能通过public.users显式引用绕过 - 验证方式:
SHOW GRANTS FOR 'app_user'@'%';,输出里不能出现ON mydb.users
别让视图变成可更新的“后门”
单表、无聚合、无表达式的视图,在 MySQL 和 PostgreSQL 中默认允许 UPDATE。用户虽看不到 is_deleted 列,但通过 UPDATE user_safe SET name = 'xxx' WHERE id = 1,可能意外恢复软删除记录。
- MySQL:默认可更新,
WITH CHECK OPTION仅校验条件,不阻止写入 - PostgreSQL:查
pg_views.definition确认是否含聚合或表达式;如需只读,显式用CREATE VIEW ... WITH LOCAL CHECK OPTION并配合GRANT撤回INSERT/UPDATE - 最稳妥做法:不授写权限,且视图定义避开可更新结构(比如加
CASE WHEN ... END AS status_masked)
脱敏字段必须重命名,别留语义陷阱
写 CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone,列名还是 phone。前端代码可能直接拿这个值做短信校验,ORM 可能反向映射更新原字段,审计工具也识别不出这是掩码值。
- 手机号应重命名为
phone_masked,身份证号应重命名为id_card_masked - 避免别名暗示原始含义,例如
id_head或ssn_prefix可能反向泄露字段用途 - 真正干净的做法:敏感字段压根不出现;需要脱敏时,优先在应用层处理,或启用数据库原生动态脱敏(如 SQL Server RLS、MySQL 8.0+ Data Masking 插件)
最容易被忽略的点是:视图定义本身可被高权限用户读取(SHOW CREATE VIEW 或查 pg_views.definition),里面明文写着底层表名、字段和函数逻辑——所谓“隐藏”,只是靠权限隔离实现的逻辑封装,不是物理隔离。

















