直接用REPLACE全文替换不可靠,应使用LEFT+RIGHT+CONCAT定位截取并显式处理NULL、长度异常和格式脏数据;身份证号需按15/18位分支处理或用REPEAT动态掩码,视图须配合权限隔离。

直接用 REPLACE 对手机号、身份证号做“全文替换”不可靠——它不认位置,只认内容,极易误替、多替、漏替。真正可控的做法是组合 LEFT、RIGHT、CONCAT 等确定性函数,按固定索引截取+拼接,并显式处理 NULL、长度异常和格式脏数据。
手机号脱敏:别碰 REPLACE,用 LEFT+RIGHT 定位截取
常见错误是写 REPLACE(mobile, SUBSTRING(mobile,4,4), '****'),一旦字段含重复数字(如 '13812121212'),中间段可能被多次替换,结果完全失真。
- 正确写法:
CONCAT(LEFT(mobile, 3), '****', RIGHT(mobile, 4)) - 必须加
IFNULL防NULL:否则整列返回NULL,前端易空指针 - 手机号带空格或短横线?先清洗:
REPLACE(REPLACE(TRIM(mobile), '-', ''), ' ', '') - 长度非 11 位时,
RIGHT(..., 4)可能返回空字符串,建议加IF(CHAR_LENGTH(...) = 11, ..., mobile)校验
身份证号脱敏:按长度分支处理,禁用硬编码星号
15 位和 18 位身份证结构不同,末位可能是 X,直接写死 '********' 会错位。且 15 位证末 3 位是顺序码,不是校验位,掩码位数应少于 18 位证。
- 安全写法用
CASE WHEN LENGTH(id_card) = 18 THEN ... WHEN LENGTH(id_card) = 15 THEN ... ELSE 'INVALID' - 更自适应的方案:
CONCAT(LEFT(id_card, 6), REPEAT('*', CHAR_LENGTH(id_card) - 10), RIGHT(id_card, 4)),自动适配两种长度 - 务必前置
TRIM(BOTH '\r\n ' FROM id_card),避免换行符干扰CHAR_LENGTH - 别用
SUBSTRING(id_card, -4)——MySQL 5.0+ 兼容性差,RIGHT(id_card, 4)更稳
视图是脱敏落地的最小可行单元,但权限隔离必须同步做死
只建视图不收基表权限,等于没脱敏。用户只要对原表有 SELECT 权限,就能绕过视图直接查明文。
-
CREATE VIEW必须显式声明DEFINER = 'admin'@'%'和SQL SECURITY DEFINER - 判断调用者身份用
USER(),不是CURRENT_USER()(后者返回的是DEFINER身份) - 敏感字段类型不同,表达式要适配:比如
phone是BIGINT,得先CAST(phone AS CHAR)再LEFT - 所有脱敏字段外层包
IFNULL(..., ''),避免NULL透出导致下游逻辑崩
触发器只用于写入清洗,不能替代查询脱敏
触发器不是动态脱敏的解决方案。它只能在 INSERT/UPDATE 时把明文转成脱敏值存进去,且不可逆——原始数据永久丢失。
- 禁止在
AFTER SELECT上挂触发器:MySQL 不支持,直接报错ERROR 1419 - 若用触发器预清洗,仅限日志表、宽表等明确不需要原始值的场景
- 核心业务表仍应保留明文,靠视图控制查询出口
- 触发器里调用函数必须声明
DETERMINISTIC,否则主从复制可能失败
最常被忽略的一点:脱敏表达式必须是确定性的。任何含 NOW()、RAND()、USER()(非用于权限判断时)的写法,都可能导致视图创建失败或主从不一致。真实生产环境里,一个没加 IFNULL 的 LEFT,就可能让整张报表前端报错;一个没清理空格的手机号,会让 RIGHT 截到空格而非数字——这些细节比函数选型更决定成败。


















