触发器强制同步执行,无法并行:1000行批量插入将串行触发1000次,每条1ms即耗1秒,tps从1000骤降至1;其共享事务上下文会隐式放大锁范围,易引发等待链与死锁。

触发器强制同步执行,根本无法并行
MySQL 的 AFTER INSERT 或 BEFORE UPDATE 触发器不是后台线程,而是和主 SQL 共享同一个事务上下文、同一线程、同一把锁。1000 行批量插入,就会触发 1000 次触发器逻辑——这不是“慢”,是硬性串行化。哪怕每条只耗 1ms,1000 次就是 1 秒,TPS 直接从 1000 掉到 1。
锁范围被隐式放大,极易形成等待链
触发器里一句 SELECT ... FROM user_profile WHERE user_id = NEW.user_id FOR UPDATE,就会让本该只锁订单行的事务,额外持有一行用户数据的 X 锁。多个并发订单更新同一用户时,立刻排队;更糟的是,如果触发器还顺手 UPDATE order_summary SET total = ...,就可能和另一笔正在更新 order_summary 的事务互相死锁。
- 常见错误现象:
SHOW PROCESSLIST里大量线程卡在Updating或Waiting for table metadata lock - 真实瓶颈不在 SQL 本身,而在
Innodb_row_lock_waits指标飙升 - 用
performance_schema.events_waits_summary_by_thread_by_event_name查wait/synch/mutex/innodb/log_flush_mutex,常能发现刷日志锁已成排队瓶颈
触发器内查表或调函数,实际是在放大事务粒度
你以为只是“查一下余额”,但 SELECT balance FROM accounts WHERE id = NEW.account_id 在 RR 隔离级别下会加 gap lock;如果 accounts 表没索引,就是全表扫描+全表加锁。而调用一个带 SELECT 的存储函数,等于把一次解析、权限检查、上下文切换、SQL 执行全塞进主事务里——实测 500 QPS 下,单次延迟可从 0.8ms 涨到 12ms。
- BEFORE INSERT 中不能用
NEW.id去查其他表(自增 ID 尚未生成) - 所有触发器内
SELECT必须有覆盖索引,且避免JOIN、IN、OR等低效写法 - 函数调用只保留加密、JSON 解析等真正必要场景,并显式声明
READS SQL DATA
真正解耦必须跳出“在数据库里做异步”的幻觉
把触发器改成调用 CALL send_to_kafka(...) 存储过程?仍是同步阻塞——MySQL 会等函数返回才提交事务。所谓“异步”,必须满足两个硬条件:主 SQL 完全不感知下游动作;消费者连接与主事务完全隔离。
- 有效路径只有一条:触发器只写极简变更记录(如
INSERT INTO sync_log (table_name, pk_value, op_type) VALUES ('orders', NEW.id, 'INSERT')),其余全由 Canal/Kafka 消费者处理 - 验证是否真解耦:关掉消费者服务,主业务写入是否照常不卡?卡了,说明还没切出去
- 幂等设计不能补救:MQ 重发是常态,
INSERT IGNORE或ON DUPLICATE KEY UPDATE是底线,不是可选项











