应使用 left()+right()+concat() 组合而非 replace(substr()) 做中间位隐藏,因后者易误替、漏替、破坏格式;需按字段长度分情况处理,update 前必须校验格式并过滤脏数据。

别用 REPLACE 配合 SUBSTR 做中间位隐藏——它在多数真实场景下不可靠,容易误替、漏替甚至破坏数据格式。
为什么 REPLACE(SUBSTR()) 组合会出错
这种写法本质是「先截一段,再全局替换所有匹配项」,但字符串内容千变万化,SUBSTR() 截出来的子串可能在字段里重复出现多次。比如:
- 手机号
'13812121212':想隐藏第4~7位(即'1212'),但该子串在末尾又出现一次,REPLACE()会把两处都替成'****',结果变成'138****1212'或更糟的'138****1***' - 身份证号含字母校验码(如
'11010119900307271X'):若用SUBSTR(id_card, 5, 8)截出'19900307',而某条数据里恰好在别处也含这串数字,就会被误替 - 字段带空格或分隔符(如
'138-1234-5678'):SUBSTR()按位置切,但REPLACE()仍会去全文找匹配,逻辑完全脱节
真正可控的中间位隐藏:用 LEFT() + RIGHT() + CONCAT()
这是 MySQL 5.0+ 全版本兼容、不依赖内容、只按位置操作的安全组合。关键点是绕过「查找-替换」逻辑,直接拼接首尾。
- 手机号(11位,隐藏第4~7位):
CONCAT(LEFT(mobile, 3), '****', RIGHT(mobile, 4)) - 邮箱(保留前3位和后4位):
CONCAT(LEFT(email, 3), REPEAT('*', GREATEST(CHAR_LENGTH(email) - 7, 0)), RIGHT(email, 4))—— 注意用GREATEST()防负数 - 必须包裹
IFNULL(mobile, ''),否则任一参数为NULL,整条结果就是NULL -
RIGHT(mobile, 4)比SUBSTR(mobile, -4)更稳,前者在 MySQL 所有主流版本中行为一致
遇到可变长度字段时,必须加长度判断
身份证号就是典型:有15位旧证和18位新证,直接套固定位置会错位。不能假设全是18位。
- 18位证:前6位(地址码)+ 后4位(校验码及末位)是稳定段,中间8位(出生年月日+顺序码)需隐藏
- 15位证:无校验码,末3位是顺序码,中间6位才是出生年月日
- 正确写法必须用
CASE WHEN LENGTH(id_card) = 18 THEN ... WHEN LENGTH(id_card) = 15 THEN ... ELSE 'INVALID' END - 别信“统一截第11~14位”这种说法——15位证根本没第14位,
SUBSTR(id_card, 11, 4)会返回空或报错
UPDATE 场景下更要警惕副作用
如果是在 UPDATE 语句里脱敏(比如批量清洗历史数据),REPLACE(SUBSTR()) 的风险会被放大:
- 原字段值被永久覆盖,无法回溯原始数据
- 若某条记录手机号只有10位,
SUBSTR(mobile, 4, 4)可能返回空或不足4位,REPLACE()却仍会执行,导致结果异常(如'138****') - 没有显式
CASE WHEN LENGTH(mobile) = 11校验,脏数据会静默污染全表 - 更新后字段长度可能突变(比如从11变12),下游系统解析失败
真正要动手改库,得先加长度过滤:WHERE mobile REGEXP '^[0-9]{11}$',再用 CONCAT(LEFT(), '****', RIGHT()) 更新,否则不如不动。










