视图定义必须显式列出字段,禁止使用select *;脱敏字段需加_masked后缀别名;where过滤不可依赖脱敏后字段,否则索引失效;视图不加密数据,需严格管控基表权限与definer权限。

视图定义必须显式列出字段,不能用 SELECT *
用 SELECT * 创建视图会把原表所有列原样暴露,哪怕你后续只查视图,列名、顺序、NULL行为全和基表一致——敏感字段如 password、id_card 依然存在,只是没被业务代码读取而已。这不是隐藏,是掩耳盗铃。
实操建议:
- 视图里必须写死字段列表,比如
CREATE VIEW user_safe AS SELECT id, username, email FROM users - 如果原表新增了
salary列,SELECT *视图会自动包含它;显式列表则完全不受影响 - MySQL 8.0+ 支持列权限,但视图不继承列权限,所以靠
GRANT SELECT(password) ON users撤权没用——必须从视图源头掐断
GRANT SELECT ON view_name 才是权限生效的关键
用户能查基表,不代表能查视图;反过来,用户没基表权限,只要被授予视图的 SELECT 权限,照样能查——前提是视图 DEFINER 有足够权限(比如 root@localhost)。这是脱敏能落地的前提。
常见错误现象:
- 建好视图后,用户执行
SELECT * FROM user_safe报错ERROR 1142 (42000): SELECT command denied—— 忘了GRANT SELECT ON user_safe TO 'analyst'@'%' - 用户能查视图,但查出来字段少了一列——可能视图定义漏写了,或
DEFINER账号本身没权限读某列(比如被列权限限制) - PostgreSQL 中需额外确认角色是否激活:
SET ROLE analyst,否则权限不生效
脱敏字段别名必须带 _masked 或 _hidden 后缀
视图里对敏感字段做函数处理,比如 CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone,列名还是 phone。下游系统或前端可能误判为明文,直接用于校验、导出、日志打印,造成二次泄露。
为什么这样做:
- 别名是语义信号,
phone_masked明确告诉调用方“这不是原始值” - 避免 ORM 自动映射时把脱敏结果当真实手机号反向更新
- 配合审计工具识别脱敏字段(有些 DLP 系统靠列名规则匹配)
注意 IFNULL(phone, '') 要包在脱敏表达式里,否则 phone IS NULL 会导致整个 CONCAT 返回 NULL,前端容易报空指针。
WHERE 过滤不能依赖脱敏后字段,否则索引失效
如果视图定义里写了 CONCAT(...) AS phone_masked,外部再写 WHERE phone_masked = '138****1234',数据库只能全表扫描——因为 phone_masked 是运行时计算值,无法走 phone 索引。
可行方案:
- 需要按手机号过滤,就别在视图里脱敏;让应用层或外层查询自己控制:视图只暴露原始
phone,查询时用WHERE phone LIKE '138%1234'+SELECT CONCAT(...)输出 - MySQL 8.0+ 可建生成列:
ALTER TABLE users ADD phone_masked VARCHAR(11) STORED AS (CONCAT(...)),再给它加索引(但注意生成列索引仅适用于等值查询,不支持LIKE) - 真要复杂过滤,用物化视图(PostgreSQL)或定时刷新的中间表,把脱敏+过滤逻辑前置
最易被忽略的一点:视图不改变表结构,也不加密数据。它只是个查询封装。如果你忘了收回基表的 SELECT 权限,或者 DEFINER 账号权限过大,视图就形同虚设。










