MySQL触发器默认逐行触发,批量INSERT会执行多次;错误源于混淆数据库类型或缺失FOR EACH ROW;禁止对NEW使用SELECT INTO;避免在触发器中调用耗时函数、无索引查询或频繁I/O;优先用INSERT ... SELECT替代简单同步逻辑。

MySQL触发器本来就会逐行触发,不需要“改成多行”
MySQL的FOR EACH ROW触发器在批量INSERT中天然就是逐行执行的——比如INSERT INTO t (a) VALUES (1),(2),(3)会调用触发器三次,不是一次。你看到“只触发一次”,大概率是误用了SQL Server或PostgreSQL的语句级触发器逻辑,或者没确认当前数据库类型。先查SELECT VERSION(), @@sql_mode,确保你真在MySQL里跑;再看触发器定义里有没有FOR EACH ROW字样,缺了这个就不是MySQL标准行级触发器。
别在触发器里写SELECT ... INTO @var取单行值
常见错误是想从NEW里捞出ID做后续处理,写成SELECT NEW.id INTO @id——这语法根本通不过,NEW不是表,不能当数据源SELECT。更糟的是有人模仿SQL Server写SELECT @id = id FROM inserted,这在MySQL里直接报错。正确做法只有两种:
- 直接用
NEW.column_name访问当前行字段,比如SET @log_msg = CONCAT('inserted: ', NEW.id) - 如果必须关联其他表(比如查用户类型),用
SELECT ... INTO @var FROM other_table WHERE id = NEW.other_id,但要注意:这种查询没有索引时会全表扫描,10万行批量插入可能卡死
批量插入性能崩盘,90%因为触发器里有隐式开销
逐行触发不等于安全,以下操作会让单行触发耗时从0.1ms飙到10ms+:
- 每次调用
NOW()、UUID()等函数,高并发下系统时钟和随机数生成器争抢严重 - 触发器内
INSERT INTO audit_log VALUES (NEW.id, NOW())这种写法,每行都走一次I/O,不如改用生成列:created_at DATETIME AS (NOW()) STORED - 对
NEW字段做函数运算,比如UPPER(NEW.name),无法利用索引且增加CPU负担
真正需要审计日志的场景,优先用INSERT ... SELECT替代触发器,或者把日志写入消息队列异步落库。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
用INSERT SELECT代替触发器,往往更稳更快
如果你的触发器逻辑本质是“插入A表时,同步往B表写一条记录”,那它大概率可以被INSERT ... SELECT替代。例如原触发器是:
CREATE TRIGGER ins_log AFTER INSERT ON orders FOR EACH ROW INSERT INTO order_logs (order_id, status) VALUES (NEW.id, 'created');
完全可以改成应用层一句:
INSERT INTO orders (user_id, amount) VALUES (123, 99.9); INSERT INTO order_logs (order_id, status) SELECT LAST_INSERT_ID(), 'created';
这样绕开了触发器的隐式事务嵌套、避免了锁竞争,还方便加监控和重试。唯一代价是业务代码多一行SQL——但比调试一个慢到超时的触发器划算得多。

















