根本原因是触发器强制将异步逻辑塞入同步事务,导致锁等待、吞吐骤降;应禁用触发器内查询,改用应用预加载或中转表+外部消费。

触发器卡在高并发写入时,根本不是“慢”,是同步阻塞
MySQL触发器在每行 INSERT/UPDATE/DELETE 时强制同步执行,且和主 SQL 共享同一事务、同一锁上下文。当写入频率超过 100 QPS,尤其带 SELECT 查询或跨表 INSERT 时,单条语句耗时很容易从 2ms 拉到 50ms+,TPS 断崖下跌——这不是配置没调好,是架构层的硬性限制。
为什么消息队列比“优化触发器”更治本
把触发器逻辑挪到 MQ(如 RabbitMQ、Kafka)消费端,本质是把「强一致性 + 同步等待」切换为「最终一致性 + 异步解耦」。这能直接绕开三类 MySQL 原生瓶颈:
- 事务内锁持有时间归零:主 SQL 提交即返回,不再等日志写完、统计更新完
- 避免触发器逐行调用放大:1000 行批量插入 → 触发器执行 1000 次;而 MQ 可以按批次消费,一次聚合更新
- 消除隐式死锁风险:AFTER 触发器里
UPDATE order_summary和其他事务争抢同一行锁的问题彻底消失
怎么落地?关键不是换工具,而是切分边界
不能简单把原触发器代码复制进消费者,必须重新划清「必须同步」和「可以异步」的逻辑边界:
-
必须同步的部分:字段默认值填充(
SET NEW.created_at = NOW())、基础校验(IF NEW.amount ),保留在 BEFORE 触发器里 - 可异步的部分:写操作日志表、更新用户总消费、推送通知、调用外部 API,全部移出,改由应用监听 binlog(用 Canal/Maxwell)或业务层发 MQ 消息
-
数据一致性兜底:MQ 消费失败需重试 + 死信队列;关键字段(如
updated_at)仍建议用GENERATED COLUMN或应用层显式传入,不依赖异步结果
容易被忽略的坑:不是所有“异步”都真异步
常见错误是把触发器改成调用存储过程,再让该过程发 MQ —— 这仍是同步阻塞,因为 MySQL 会等函数返回才提交事务。真正有效的异步必须满足:
- 主 SQL 不感知下游动作:消息由应用层(非触发器)发出,或通过
BINLOG解析器捕获后投递 - 消费者与数据库连接隔离:不要复用主事务的连接,避免连接池耗尽
- 幂等设计必须前置:MQ 重发会导致重复消费,
INSERT ... ON DUPLICATE KEY UPDATE或去重表INSERT IGNORE是底线
最简验证方式:关掉消费者服务,看主业务是否照常写入不卡顿。如果卡,说明还没真正解耦。











