直接用REPLACE脱敏错误,应使用LEFT+RIGHT+CONCAT定位截取并显式处理NULL、长度异常和脏数据;手机号用CONCAT(LEFT(mobile,3),'*',RIGHT(mobile,4))并清洗格式、校验长度;身份证宜用CONCAT(LEFT(id_card,6),REPEAT('',CHAR_LENGTH(id_card)-10),RIGHT(id_card,4))自适应掩码,配合TRIM和权限隔离视图落地。

直接用 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(cleaned_mobile) = 11, CONCAT(...), cleaned_mobile)
身份证号脱敏:按长度分支处理,禁用硬编码星号
15 位和 18 位身份证结构不同,末位可能是 X,直接写死 '********' 会错位;且 15 位证末 3 位是顺序码,不是校验位,掩码位数应更少。
- 稳妥做法用
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 - 如果业务需要可逆脱敏(如审计场景),必须保留原始字段,脱敏仅用于展示层
- 日志、慢查、备份中仍可能暴露明文,脱敏不能代替访问控制和加密存储
最常被忽略的一点:脱敏表达式里每个函数调用都要考虑 NULL 传播链——LEFT(NULL, 3) 返回 NULL,CONCAT(NULL, '****') 还是 NULL,最终整行失效。所以 IFNULL 不是可选项,是必选项。


















