before insert/update 中只能用单值子查询校验关联表状态并配合 signal 中断非法写入,不可用 join;after insert 才能安全跨表同步,且字段类型必须严格一致。

BEFORE INSERT/UPDATE 里只能用子查询校验,不能写 UPDATE
跨表校验必须放在 BEFORE INSERT 或 BEFORE UPDATE 阶段,靠单值子查询查关联表状态,再用 SIGNAL 中断非法写入。比如订单插入前检查用户是否被禁用:
SELECT status INTO @user_status FROM users WHERE id = NEW.user_id LIMIT 1; IF @user_status IS NULL OR @user_status != 'active' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'user not active'; END IF;
- 必须加
LIMIT 1,否则多行返回直接报错中断 - 不能用
JOIN,MySQL 在触发器中对触发表以外的表做 JOIN 容易锁升级、拖慢主表写入 - 子查询字段必须有索引,否则每插一行都扫全表,QPS 上百就卡住
-
NEW.user_id是空或无效时,子查询返回NULL,!= 'active'判定为UNKNOWN,需显式写成IS NULL OR status != 'active'
AFTER INSERT 才能安全同步目标表,且字段类型必须严格一致
AFTER INSERT 是唯一能拿到完整自增 ID 和默认值的时机,适合往日志表、摘要表插入关联记录。但目标表字段类型必须和源表完全匹配,否则隐式转换会截断或报错:
- 源表
orders.id是BIGINT UNSIGNED,目标表order_logs.order_id也得是BIGINT UNSIGNED,写成INT可能丢数据 - 字符串字段要防空格干扰:
TRIM(NEW.phone)再比对,避免因前后空格导致校验失败 - 禁止在
AFTER触发器里对原表做任何操作(如UPDATE orders SET updated_at = NOW()),MySQL 5.7+ 直接报ERROR 1442 - 想自动补全字段?只能在
BEFORE阶段设NEW.updated_at = NOW(),不能靠AFTER回写
UPDATE 同步必须判断字段是否真变了,别一动就刷
很多触发器一更新就无差别同步,结果时间戳字段改一次,冗余表跟着刷一遍。真正影响业务的字段才值得响应:
- 用
OLD.status != NEW.status判断,而不是OLD.* != NEW.*(含NULL时恒为FALSE) - 字符串比较前强制
TRIM(),数值字段注意单位统一(比如金额存分为单位,别拿元去比) - 聚合类同步(如统计明细行数)必须加
SELECT ... FOR UPDATE锁主记录,否则并发下计数错乱 - 别在触发器里调用存储过程或外部 HTTP 接口——事务上下文里超时或失败,整个写入就卡死
DELETE 触发器最容易漏掉外键约束和幂等性
BEFORE DELETE 或 AFTER DELETE 里清理关联数据,看似简单,实则常踩两个坑:
- 如果目标表有外键且设了
ON DELETE CASCADE,再在触发器里手动删一遍,可能报“记录不存在”错误;先确认外键策略是否已覆盖 -
INSERT IGNORE和REPLACE INTO对跳过的行完全不触发任何触发器,你以为的“自动纠偏”,在批量导入时根本没生效 - 删除前校验子表是否为空,要用
EXISTS (SELECT 1 FROM order_items WHERE order_id = OLD.id),别用COUNT(*) > 0,性能差一个数量级 - 触发器函数结尾必须写
RETURN OLD;(MySQL/PostgreSQL),漏写会导致删除静默失败,数据还在,但你以为删了
最常被忽略的不是语法,而是触发器根本不在所有写入路径上运行:ETL 工具直连、DBA 临时脚本、ORM 的 bulk_create 全部绕过它。所谓“自动纠偏”,只对走标准 INSERT/UPDATE/DELETE 的那部分流量有效。











