必须用BEFORE而非AFTER触发器,因为AFTER无法修改NEW值,赋值会被静默忽略;字段须为VARBINARY/BLOB以存二进制密文;密钥须由会话变量传入且长度合规;NULL和空字符串需显式判断避免覆盖;触发器预加密不可逆,影响审计与密钥轮换。

不能用触发器做“查询时脱敏”,只能在写入时预处理;用 BEFORE INSERT/UPDATE 修改 NEW 值,且必须配合字段类型、密钥管理、NULL 判断等硬性约束,否则加密失败、主从断裂、解密为空全是常态。
为什么必须用 BEFORE 而不是 AFTER 触发器
AFTER INSERT 和 AFTER UPDATE 触发器无法修改当前行数据。MySQL 会静默忽略对 NEW.phone 的赋值操作——你写的 SET NEW.phone = AES_ENCRYPT(...) 完全不生效,查出来还是明文,或者解密时得到 NULL。
-
BEFORE INSERT是唯一能拦截并改写NEW值的时机,确保加密/掩码发生在数据落盘前 - 试图在
AFTER里再UPDATE同一张表,会触发错误:Can't update table in stored function/trigger -
AFTER只适合日志记录、通知、异步清理等副作用操作,不能用于数据清洗
字段类型必须是 VARBINARY 或 BLOB 才能存加密结果
AES_ENCRYPT() 返回的是二进制数据,不是字符串。如果目标字段定义为 VARCHAR(255),MySQL 会按字符集(如 utf8mb4)强行转码,导致乱码或截断,后续 AES_DECRYPT() 必然失败。
- 建表时明确指定:
phone VARBINARY(255),不能写成VARCHAR - 已有表需执行:
ALTER TABLE users MODIFY phone VARBINARY(255),仅用CHANGE改名无效 - 若业务强依赖字符串模糊查询(如
WHERE phone LIKE '%138%'),加密就不适用——该换掩码视图
密钥绝不能硬编码,必须由会话变量传入
把密钥写死在触发器 SQL 里,等于把保险柜密码刻在锁芯上。SHOW CREATE TRIGGER 会完整暴露密钥,任何有 TRIGGER 权限的人都能看见。
- 应用层每次写入前必须先设变量:
SET @encryption_key = 'your-32-byte-key-here'; - 触发器内只调用:
AES_ENCRYPT(NEW.phone, @encryption_key),不拼接、不设默认值 - 密钥长度必须合规:AES-256 要 32 字节;传入字符串会被自动补零或截断,建议用
LENGTH(UNHEX('...'))校验 - 禁止在触发器里查表取密钥——MySQL 禁止跨表读写,报错:
Can't update table in stored function/trigger
NULL 和空字符串必须显式跳过,否则覆盖原始值
对 NULL 或空串执行 AES_ENCRYPT(),结果是 NULL。更危险的是:如果字段允许 NULL,而触发器没判断,SET NEW.phone = AES_ENCRYPT(NULL, @k) 会把原本的非空值覆盖成 NULL,原始数据永久丢失。
- 触发器逻辑必须包含显式判断:
IF NEW.phone IS NOT NULL AND NEW.phone != '' THEN - 别依赖隐式转换,
IF NEW.phone在 MySQL 中对空字符串也返回 false,不可靠 - 一旦用了触发器预清洗,原始值就不可逆;审计回溯、风控比对、密钥轮换解密——这些需求都会落空
真正容易被忽略的点是:触发器修改的是数据本身,不是展示方式。它不区分写入者身份、不支持按角色动态脱敏、历史数据无法批量重跑规则。除非业务明确不需要原始值(比如日志表),否则优先用视图 + 权限控制,而不是触发器。

















