视图脱敏必须手动写死逻辑并彻底回收权限,如手机号用CONCAT(LEFT(TRIM(phone),3),'**',RIGHT(TRIM(phone),4)),身份证需TRIM+CHECK约束,禁用非确定函数,设SQL SECURITY DEFINER,撤基表权限,禁用INVISIBLE COLUMN。

视图里必须写死脱敏逻辑,不能留裸字段
视图不是开关,它不自动“隐藏”字段,而是靠你在 SELECT 里手动构造脱敏结果。比如手机号不能直接写 phone,得写成 CONCAT(LEFT(TRIM(phone), 3), '****', RIGHT(TRIM(phone), 4));身份证号别用 RIGHT(id_card, 4),要改用 RIGHT(TRIM(id_card), 4) 并配合建表时的 CHECK 约束,否则末位 X 会导致长度错乱。
常见错误是只建个空壳视图:SELECT id, name, phone FROM users,然后指望应用自己处理脱敏——这等于把策略散落在代码里,一改格式就得全量修。
- 每个脱敏字段都要包
IFNULL(, ''),防止NULL让整个CONCAT结果变空 - 所有字符串操作前加
TRIM(),避免空格干扰截取逻辑 - 禁用
NOW()、CURRENT_USER()、RAND()等非确定性函数,MySQL 会直接报ERROR 1351
权限必须撤干净,只给视图不撤基表等于白干
MySQL 权限是白名单制,但旧权限不会自动过期。用户只要还持有 SELECT ON mydb.users,就能绕过视图直查原表——哪怕你只授了 SELECT ON mydb.v_users_safe。
验证是否生效:执行 SHOW GRANTS FOR 'app_user'@'%',输出里不能出现任何指向基表(如 mydb.users)或宽泛库级(如 mydb.*)的 SELECT 权限。
- 先执行
REVOKE SELECT ON mydb.users FROM 'app_user'@'%' - 再执行
GRANT SELECT ON mydb.v_users_safe TO 'app_user'@'%' -
FLUSH PRIVILEGES不需要——GRANT/REVOKE自动刷新内存缓存
SQL SECURITY DEFINER 是硬性要求,别用默认值
默认 SQL SECURITY INVOKER 会让视图以调用者身份执行,如果该用户意外拥有其他高权限表的访问权,可能通过子查询或 JOIN 窃取数据。设为 DEFINER 才能锁定执行上下文。
创建时必须显式声明,例如:CREATE VIEW v_users_safe AS SELECT ... SQL SECURITY DEFINER。DEFINER 账号需有基表 SELECT 权限(通常是 DBA 或专用账号),但该账号本身不应开放给应用使用。
- 别用
root或管理员账号当DEFINER,否则低权限用户也能透过视图拿到原始数据 -
DEFINER账号只需基表读权限,不需要CREATE VIEW或其他管理权限 - 视图中禁止引用其他表的子查询(除非是单表投影),否则可能触发权限绕过
别碰 INVISIBLE COLUMN,它不是安全机制
INVISIBLE COLUMN 只是语法糖,对 SELECT * 不可见,但只要明确写出字段名(如 SELECT phone FROM users),就能照常读到;而且权限系统完全不感知它,GRANT SELECT(phone) 依然生效。
它无法替代视图 + 权限回收组合,也不能防止索引暴露字段内容——脱敏字段本身无法被索引,这点必须提前意识到。
- 真正敏感字段(如
id_card、password)应从物理设计上就隔离:密码列必须走bcrypt加盐哈希,不能用SHA2()或MD5()直接套用 - 视图里做不可逆脱敏(如
SHA2(phone, 256))只适合统计比对场景,不适合需要还原的业务 - 高频检索字段仍要保留原始列,并靠列级权限(MySQL 8.0+)或应用层拦截控制访问


















