mysql触发器是绑定在表上、对insert/update/delete自动响应的程序,按行触发而非按语句触发;before用于校验或改写数据,after用于日志记录等不可逆操作。

触发器就是“表一动,它就自动跑”的数据库小助手
MySQL 触发器不是你手动调的存储过程,也不是定时任务;它是绑定在某张表上的、对 INSERT、UPDATE 或 DELETE 操作做出响应的自动程序。只要那张表有行被改(哪怕只改 1 行),触发器就会按设定时间(BEFORE 或 AFTER)执行一次——对每一行都触发,不是对整条 SQL 语句触发。
为什么非得用 BEFORE UPDATE 而不是 AFTER?
关键看你要干什么:
- 想校验或改写即将写入的数据(比如把空
email改成'unknown@example.com'),必须用BEFORE UPDATE——因为NEW字段还能改,改完才会真正落库; - 想记录日志、同步更新另一张表(比如用户资料改了,顺便更新统计表里的
last_updated),用AFTER UPDATE更安全,避免事务回滚时日志已写但主数据没生效; -
AFTER里不能修改NEW值,否则报错Can't update table 'xxx' in stored function/trigger; - 如果触发器里要查当前正在被更新的同一张表,
BEFORE可能绕过限制(但极不推荐,易死锁)。
创建 UPDATE 触发器最容易踩的三个坑
新手写 CREATE TRIGGER ... UPDATE ON xxx 时,常卡在这几个地方:
- 忘了设分隔符:
DELIMITER $$必须在CREATE TRIGGER前加,否则遇到第一个;就终止解析,报语法错误; - 误用
OLD和NEW:OLD.column是更新前的值(只读),NEW.column是更新后的值(BEFORE中可写,AFTER中只读); - 触发器名重复或跨库冲突:触发器名在**同一个数据库内必须唯一**,不能和函数、存储过程同名,也不能用反引号或点号命名(如
`my.trigger`会报错)。
一个真实可用的 UPDATE 触发器示例
假设有一张 users 表,要求每次更新用户邮箱时,自动在 user_audit 表里记一条变更日志:
DELIMITER $$
CREATE TRIGGER log_email_change
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
IF OLD.email != NEW.email THEN
INSERT INTO user_audit (user_id, action, old_value, new_value, updated_at)
VALUES (NEW.id, 'email_update', OLD.email, NEW.email, NOW());
END IF;
END$$
DELIMITER ;
注意这里用了 IF 判断字段是否真变了——避免无意义日志刷屏;也避开了在 AFTER 里操作原表的风险。这种“有变化才记录”的逻辑,是生产环境触发器的基本修养。
触发器本身不难写,难的是想清楚它该在哪个时间点介入、改什么、查什么、会不会和主业务事务互相拖住。上线前务必在事务里测回滚场景,否则可能发现日志写了但主数据丢了,或者主数据改了但触发器卡死整个更新链路。










