必须用BEFORE UPDATE触发器计算MD5,因AFTER阶段NEW已写入无法修改;需用SET NEW.col=MD5(NEW.field)赋值,NULL需显式处理,推荐封装DETERMINISTIC函数复用逻辑。

UPDATE 时触发器调用 MD5() 的基本写法
必须用 BEFORE UPDATE,不能用 AFTER UPDATE。因为 AFTER 阶段 NEW 已写入,再改 NEW.md5_col 没有效果;而 BEFORE 阶段允许你修改即将插入/更新的值。
假设表 users 有原始字段 email,要自动填充 email_md5:
DELIMITER // CREATE TRIGGER users_email_md5_before_update BEFORE UPDATE ON users FOR EACH ROW BEGIN SET NEW.email_md5 = MD5(NEW.email); END// DELIMITER ;
- 触发器名建议带表名+字段+时机,避免重名或误删
-
NEW.email是本次 UPDATE 中用户传入的新值,不是旧值(OLD.email才是) - 如果
email允许为NULL,MD5(NULL)返回NULL,不会报错但可能不符合业务预期,需显式判断
INSERT 和 UPDATE 要共用同一套逻辑?别写两个触发器
常见错误是分别建 BEFORE INSERT 和 BEFORE UPDATE 两个触发器,逻辑重复、维护成本高,还容易漏掉一个。
更稳妥的做法是:把核心计算逻辑封装成函数,两个触发器都调它。
先创建函数:
DELIMITER //
CREATE FUNCTION calc_email_md5(input_email VARCHAR(255))
RETURNS CHAR(32)
READS SQL DATA
DETERMINISTIC
BEGIN
IF input_email IS NULL THEN
RETURN NULL;
ELSE
RETURN MD5(TRIM(input_email));
END IF;
END//
DELIMITER ;
再在两个触发器里复用:
SET NEW.email_md5 = calc_email_md5(NEW.email);
-
DETERMINISTIC是必须的,否则 MySQL 不允许在触发器中调用该函数 -
TRIM()很关键——前端或导入脚本常带空格,不清理会导致相同邮箱生成不同 MD5 - 函数比内联 SQL 更易测试、复用,也方便后续替换为
SHA2(..., 256)
为什么 MD5() 在触发器里有时返回空或意外值?
这不是 bug,而是数据状态没理清。最常踩的三个坑:
-
NEW.column在触发器里还没被真正赋值——比如你用UPDATE users SET email = 'a@b.c' WHERE id = 1,但没在 SET 子句里显式列出email_md5,那NEW.email_md5初始就是NULL;触发器里SET NEW.email_md5 = ...才真正给它赋值 - 字符集影响结果:
MD5('中文')在utf8mb4和latin1下字节序列不同,哈希值必然不同。确保源字段和连接字符集一致(推荐全程用utf8mb4_unicode_ci) - 隐式类型转换:如果
email是TEXT类型,而你拼接了其他字段(如CONCAT(NEW.email, NEW.salt)),MySQL 可能截断超长内容,导致 MD5 失效
校验列要不要加索引?看你怎么用
如果你只用 email_md5 做等值查询(比如反查某邮箱是否存在),加索引能明显提速;但如果只是存着备用、几乎不查,就别加——索引会拖慢每次 INSERT/UPDATE 性能,尤其高并发写场景。
加索引的写法:
ALTER TABLE users ADD INDEX idx_email_md5 (email_md5);
- 不要用前缀索引(如
email_md5(16)),CHAR(32)完整长度才 32 字节,没必要截断 - 如果该列同时用于 JOIN 或 GROUP BY,考虑是否需要联合索引,而不是孤立加单列索引
- 上线前务必在从库上执行
SHOW PROCESSLIST观察触发器是否引发锁等待——有些老版本 MySQL 在触发器里访问其他表会升级为表级锁
MD5() 计算发生在事务上下文中,一旦后续语句失败回滚,这个 MD5 值也跟着消失。它不是“最终态”,只是中间态。如果你需要跨事务、跨服务保证一致性(比如同步到 ES 或 Kafka),得靠应用层补全,触发器做不到。


















