MySQL触发器中多条件判断的关键在于“该不该写、怎么写才不出事”:单层IF安全,但嵌套、查表、改表、COUNT()或跨状态校验即属架构越界;BEFORE可修改NEW字段但不可读写本表其他行,AFTER可读OLD/NEW并写日志表但不可改当前行;字段变更检测需处理NULL,推荐用函数封装或CONCAT_WS;含子查询必须用EXISTS而非COUNT(),且依赖联合索引;多表JOIN类复杂逻辑应移至存储过程,触发器仅作轻量调度。

MySQL触发器里写多条件判断,不是“能不能”,而是“该不该写、怎么写才不出事”。简单说:单层 IF NEW.status = 'paid' AND NEW.amount > 100 没问题;但一旦出现嵌套、查表、改表、COUNT(*) 或跨状态校验,就该立刻停手——这不是语法限制,是架构越界。
BEFORE/AFTER 中多条件组合的写法差异
同一个逻辑,在 BEFORE 和 AFTER 触发器里写法和风险完全不同:
-
BEFORE INSERT可安全修改NEW字段(如补默认值、截断字符串),但不能读写本表其他行,也不能调用会修改本表的存储过程 -
AFTER UPDATE可读OLD和NEW,也能往日志表或关联表写数据,但不能再改当前行(NEW已提交) - 想判断“用户上次订单距今是否超30天”,
BEFORE里用SELECT ... FROM orders WHERE user_id = NEW.user_id ORDER BY created_at DESC LIMIT 1是允许的,但必须确保orders(user_id, created_at)有索引,否则锁表风险极高
多个字段变更检测的正确姿势
别靠 IF OLD.status != NEW.status OR OLD.type != NEW.type 这种裸比较——NULL 值会让整个表达式返回 NULL,条件直接失效。
- 用
IF (OLD.status IS NULL) != (NEW.status IS NULL) OR OLD.status != NEW.status才能真正捕获变化 - 更稳妥的做法是封装成函数:
CREATE FUNCTION field_changed(v_old TEXT, v_new TEXT) RETURNS BOOLEAN DETERMINISTIC RETURN IFNULL(v_old != v_new, TRUE);,然后在触发器里写IF field_changed(OLD.status, NEW.status) - 如果要同时检测 3 个字段是否任意一个变了,不要写三层
OR,改用CONCAT_WS('|', OLD.a, OLD.b, OLD.c) != CONCAT_WS('|', NEW.a, NEW.b, NEW.c)(前提是字段不含分隔符|)
含子查询的多条件必须用 EXISTS 而非 COUNT(*)
这是线上最常踩的性能坑。哪怕只有一层 IF,里面用 SELECT COUNT(*) > 0 就等于给每条插入加了个全表扫描。
- 错误写法:
IF (SELECT COUNT(*) FROM users WHERE id = NEW.user_id AND is_vip = 1) > 0 THEN ... - 正确写法:
IF EXISTS (SELECT 1 FROM users WHERE id = NEW.user_id AND is_vip = 1) THEN ... - 关键前提:
users(id, is_vip)必须有联合索引,否则EXISTS也慢——没有索引的EXISTS和COUNT(*)性能没区别 - 如果条件涉及多表 JOIN,比如“用户所在部门是否启用风控”,别在触发器里写
JOIN,抽到存储过程里,触发器只调用CALL check_risk_dept(NEW.user_id)
真正难的从来不是语法,而是判断“这个逻辑到底该不该塞进触发器”。只要出现“需要查多张表 + 更新另一张表 + 校验业务规则”的组合,就说明它已经不属于触发器职责范围——那是存储过程该干的事。留个空触发器壳子,把核心逻辑全挪出去,反而更稳、更快、更容易测试。


















