触发器导致insert变慢的本质是同步阻塞,因其在事务内串行执行且不支持异步i/o;应改用after insert写入轻量中转表,再由外部消费者异步处理。

触发器让 INSERT 变慢,本质是同步阻塞
MySQL 触发器在 INSERT 执行期间同步运行,事务不提交,触发器逻辑就卡在那——哪怕只是写一条日志或调个 SELECT,都会拖慢主流程。这不是“有点慢”,而是写入吞吐直接掉一个数量级的常见现象。
典型表现:SHOW PROCESSLIST 里看到大量 Updating 或 Sending data 状态卡住;慢查询日志里 INSERT 的执行时间远超语句本身开销。
- 触发器里做网络请求、远程 API 调用?绝对禁止——MySQL 不支持异步 I/O,会彻底锁死当前连接
- 触发器里查大表(尤其没走索引)?等价于把查询压力塞进写事务,容易引发锁等待甚至死锁
- 多个触发器嵌套?执行顺序不可控,调试成本高,性能衰减非线性放大
用 AFTER INSERT + 消息队列解耦写入链路
想真正“异步”,就得把耗时操作移出事务。MySQL 本身不提供异步触发器,但可以用 AFTER INSERT 触发器把关键数据写进一张轻量级中转表(比如 trigger_queue),再由外部消费者拉取处理。
这个中转表要极简:只存必要字段(如 id、table_name、event_type、created_at),引擎用 MEMORY 或带 innodb_buffer_pool_size 优化的 InnoDB,避免磁盘 I/O 成瓶颈。
- 触发器体只做
INSERT INTO trigger_queue (id, table_name) VALUES (NEW.id, 'orders')—— 一行 SQL,无条件,无子查询 - 消费者进程(Python/Go 写的常驻服务)定时或长轮询
trigger_queue,处理完立刻DELETE对应记录 - 消费者失败需重试机制,但中转表本身不能成为单点——加
status字段标记处理状态,避免重复消费
INSERT DELAYED 已废弃,别踩这个坑
MySQL 5.6 开始就废弃了 INSERT DELAYED,8.0 完全删除。有人误以为它能“异步插入”,其实它只是把插入请求扔进服务器内存队列,仍由 MySQL 主线程串行处理,且不支持事务、不返回自增 ID、不触发 AFTER 触发器——和你要的异步完全不是一回事。
现在还搜到教程推荐它,基本是照搬十年前文档。真实环境用它,轻则报错 ERROR 1062 (42000): INSERT DELAYED is not supported for table 'xxx',重则导致数据丢失或主从不一致。
- 替代方案只有上面说的“中转表 + 外部消费者”,或升级架构用 CDC(如 Debezium)捕获 binlog
- 如果业务真对延迟极度敏感,考虑把触发逻辑前置到应用层:应用写完主表后,异步发消息,不等结果返回
binlog 解析比触发器更可靠但更重
绕过触发器、直接监听 binlog 是更干净的异步方案,像 maxwell 或 canal 就干这事。它不侵入业务 SQL,也不增加事务负担,还能拿到完整变更上下文(OLD/NEW 值)。
代价是运维复杂度上升:要部署解析服务、管理 offset、处理主从切换、应对 DDL 变更。小团队容易低估这些隐性成本。
- 适合场景:多系统需要同一份数据变更(比如同步到 ES + 推送消息 + 更新缓存)
- 不适合场景:只有一两个简单通知逻辑,硬上 binlog 解析反而让故障面变大
- 注意
binlog_format = ROW必须开启,STATEMENT格式下部分变更无法准确还原
异步不是加个“async”关键字就完事。核心在于判断哪部分逻辑必须强一致(比如扣库存),哪部分可以弱一致(比如发通知、写日志)。触发器里的代码,90% 都属于后者——把它拽出来,放到事务之外,才是实际可落地的改善。











