必须在BEFORE INSERT或BEFORE UPDATE触发器中用LEFT()+CONCAT()等确定性函数做掩码,AFTER触发器无法修改NEW值;加密需VARBINARY字段、动态密钥及空值校验。

只能在 BEFORE INSERT 或 BEFORE UPDATE 触发器里做,且必须用确定性字符串函数拼接掩码,不能加密后存进 VARCHAR 字段——否则查出来全是乱码或 NULL。
为什么 AFTER 触发器改不了数据
MySQL 在 AFTER INSERT 或 AFTER UPDATE 中对 NEW.xxx 赋值会静默失败。你写 SET NEW.phone = '***';,执行完查表还是原值。这不是 bug,是设计限制:只有 BEFORE 阶段才能拦截并重写即将写入的行。
- 误用
AFTER是新手最常踩的坑,现象是“脱敏没生效”,但触发器日志又不报错 - 如果真想在插入后补日志或通知,
AFTER没问题;但数据清洗、掩码、加密,必须切回BEFORE - 试图在
AFTER里再UPDATE同一张表,会直接报Can't update table in stored function/trigger
LEFT() + CONCAT() 是掩码唯一靠谱写法
别用 REPLACE() 或正则替换手机号、身份证号——它们会误匹配重复数字(比如 '13812341234' 中间 '1234' 出现两次),也慢。固定长度字段用截取+拼接最稳。
- 手机号(11位):
IF LENGTH(NEW.phone) = 11 THEN SET NEW.phone = CONCAT(LEFT(NEW.phone, 3), '****', RIGHT(NEW.phone, 4)); END IF; - 身份证号(18位,末尾可能是
X):IF NEW.id_card IS NOT NULL AND LENGTH(NEW.id_card) = 18 THEN SET NEW.id_card = CONCAT(LEFT(NEW.id_card, 6), '********', RIGHT(NEW.id_card, 1)); END IF; - 人名(保留首字):
IF CHAR_LENGTH(NEW.name) > 1 THEN SET NEW.name = CONCAT(LEFT(NEW.name, 1), REPEAT('*', CHAR_LENGTH(NEW.name) - 1)); END IF; - 所有判断必须显式写
IS NOT NULL和长度校验,否则空值或短字符串会导致触发器中断或字段被设成NULL
加密存储要过三关:类型、密钥、NULL
如果真要 AES 加密(比如合规要求可逆),VARCHAR 字段会直接报废。MySQL 的 AES_ENCRYPT() 返回的是二进制,不是字符串。
- 字段类型必须是
VARBINARY(255)或BLOB,建表或ALTER TABLE时就得定死,CHANGE改名不改变类型 - 密钥绝不能硬编码在触发器里——
SHOW CREATE TRIGGER会全量暴露,所有人可见;必须由应用层用SET @key = '...';传入,触发器只调AES_ENCRYPT(NEW.data, @key) - 对
NULL或空串执行AES_ENCRYPT()结果是NULL,但后续AES_DECRYPT()会返回NULL,无法区分是“原始值为空”还是“解密失败”。务必加IF NEW.data IS NOT NULL AND NEW.data != '' THEN ...
真正麻烦的不是写法,而是字段类型和密钥管理——一个 VARCHAR 改成 VARBINARY 可能导致所有已有查询报错,而密钥轮换意味着所有触发器得重建。多数业务其实只需要掩码,别一上来就碰加密。











