触发器导致连接池耗尽是因为其在事务中同步执行且阻塞连接释放。需移除CALL存储过程、大表扫描、外部依赖三类操作,改用异步方案:触发器仅写sync_event_log表,再通过Canal/Kafka异步处理。

触发器为什么会让连接池耗尽
触发器在事务中同步执行,只要主 SQL 没提交,连接就一直被占用。一旦触发器里有耗时操作(比如调用 dblink、查大表、发 HTTP 请求),整个事务就会卡住,连接无法归还池中。常见现象是 HikariPool 的 active=80 idle=0,SHOW PROCESSLIST 里大量语句状态为 Updating 或 Waiting for table metadata lock,而 Info 列能看到触发器名或其内部 SQL。
哪些操作必须从触发器里移除
以下三类操作在触发器中等于主动堵死连接池:
-
CALL其他存储过程(尤其含事务或循环)——会继承父事务上下文,锁持有时间翻倍 -
INSERT INTO ... SELECT扫描未索引的大表(如七千万行的is_deleted = 0条件)——触发器内执行等同于主 SQL 多套一层全表扫描 - 任何外部依赖:
SELECT FROM remote_db.table、自定义函数调用系统命令、甚至 JDBC 中的httpclient请求——网络抖动或下游超时直接让连接卡死
怎么改才能真正释放连接
核心思路:把“必须做”和“可以晚点做”分开。触发器只干一件事——快速记一笔,其余全交给异步消费者。
- 创建一张极简表
sync_event_log,字段仅含event_id、table_name、pk_value、op_type、created_at,引擎用InnoDB,主键明确,不加冗余索引 - 触发器里只写
INSERT INTO sync_event_log VALUES (),不查关联表、不JOIN、不调函数、不判断业务逻辑 - 用
Canal(MySQL)或pg_recvlogical(PostgreSQL)监听 binlog / logical decoding,把新增行转成 Kafka 消息 - 独立服务消费 Kafka,按需做跨库更新、缓存刷新、ES 写入等——这些失败可重试,不影响主库事务
最容易被忽略的细节
很多人以为“禁用触发器”或“加索引”就能解决问题,其实关键在于锁生命周期是否可控。例如:
-
BEFORE UPDATE中用SET NEW.updated_at = NOW()是安全的,但若在此之后又SELECT FOR UPDATE查另一张表,就可能引入额外锁等待 - MySQL 的
FOR EACH ROW在批量导入 1000 行时会执行 1000 次,而FOR EACH STATEMENT只执行 1 次——但 MySQL 8.0+ 才支持 transition tables,老版本只能靠 CTE + INSERT SELECT 模拟 - 临时禁用触发器排查问题时,用的是
ALTER TABLE t DISABLE TRIGGER tr_name,不是DROP;恢复时别忘了ENABLE,否则上线后数据不一致

















