必须用BEFORE而非AFTER,因为NEW在AFTER中仅为只读快照,SET赋值无效且可能触发ERROR 1442;只有BEFORE阶段才能拦截并改写即将写入的数据行。

只能在 BEFORE INSERT 或 BEFORE UPDATE 触发器中用 SET NEW.column 动态赋值;AFTER 里写无效,还会报错。
为什么必须用 BEFORE 而不是 AFTER?
MySQL 的 NEW 在 AFTER 触发器中是只读的。你写 SET NEW.created_at = NOW() 不会生效,还可能触发 Can't update table 'xxx' in stored function/trigger 错误。只有 BEFORE 阶段才能真正“拦截并改写”即将写入的数据行。
常见错误是逻辑写对了,但触发器类型选错——比如想自动补 amount_total,却建成了 AFTER INSERT,结果字段还是 NULL 或 0。
- 建触发器前先确认语句开头是
CREATE TRIGGER ... BEFORE INSERT ON ...或BEFORE UPDATE - 用
SHOW TRIGGERS LIKE 'table_name'检查已存在触发器的时机类型 - 不要依赖
AFTER去“修正”数据——它没这个能力
如何安全地拼接字符串或做条件计算?
MySQL 不允许在列定义里用表达式设默认值(比如 DEFAULT CONCAT(first_name, ' ', last_name)),但触发器可以。关键是要处理 NULL:任何字段为 NULL,CONCAT() 结果就是 NULL。
正确做法是用 COALESCE() 替换空值,并加 IF 判断避免覆盖用户显式传入的值:
CREATE TRIGGER format_fullname_before_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.fullname IS NULL THEN
SET NEW.fullname = CONCAT(COALESCE(NEW.first_name, ''), ' ', COALESCE(NEW.last_name, ''));
END IF;
END;- 不检查
IS NULL就直接SET,会把用户本意存的fullname(比如导入历史数据时指定的)给冲掉 - 别用
= ''或IS NULL OR = ''判断字符串字段——''和时间/数字类型比较会隐式转成 0,导致误判 - UPDATE 触发器同理,但注意:如果只想在某些字段变更时才重算,得加
IF NEW.field != OLD.field判断
哪些操作绝对不能在触发器里做?
MySQL 触发器有硬性限制,踩中就会报错 ERROR 1442:
- 禁止对当前表执行
SELECT(包括SELECT MAX(id)、SELECT COUNT(*)) - 禁止在
BEFORE触发器里对本表做INSERT、UPDATE、DELETE - 禁止跨表查询时 JOIN 当前表(哪怕只是 SELECT FROM other_table JOIN target_table)
所有计算必须基于 NEW 行内已有字段,或确定性函数(如 NOW()、CONCAT()、COALESCE())。例如订单明细表要填 line_total,只能用 NEW.price * NEW.quantity,不能去查订单主表的折扣率。
和 DEFAULT 相比,触发器适合什么场景?
DEFAULT 简单高效,但只能是静态值或数据库内置函数(如 CURRENT_TIMESTAMP);触发器是唯一能做“字段间依赖计算”或“业务规则判断”的手段。
比如:status 字段要根据 order_amount 自动设为 'High Value' 或 'Regular',就必须用触发器:
CREATE TRIGGER set_status_before_insert
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
IF NEW.status IS NULL THEN
IF NEW.order_amount > 100 THEN
SET NEW.status = 'High Value';
ELSE
SET NEW.status = 'Regular';
END IF;
END IF;
END;真正容易被忽略的是:触发器逻辑一旦上线,就绕过了应用层校验。如果后续表结构变更(比如新增一个影响计算的字段),而触发器没同步更新,填充结果就会静默出错——这种问题往往在线上跑了几个月后才暴露。


















