触发器中使用sleep()会同步阻塞连接,导致事务不提交、锁不释放、mdl锁排队、连接池耗尽,表面无报错但实际卡死。

触发器里用 SLEEP() 会阻塞整个连接,不是“慢”,是“卡死”
因为 SLEEP() 是同步阻塞函数,MySQL 执行它时会把当前线程(即整个连接)挂起指定秒数,期间不释放锁、不提交事务、不响应任何新请求。哪怕只在触发器里写 SLEEP(1),这条连接就会在事务中空转 1 秒——而如果这个触发器被高频 DML 触发(比如每秒上千次 INSERT),连接池里的活跃连接会迅速耗尽,后续请求全部排队等待可用连接。
SLEEP() 在触发器里破坏的是事务边界和连接生命周期
触发器运行在父语句的事务上下文中,SLEEP() 并不结束事务,只是暂停执行。这意味着:
- 行锁/间隙锁持续持有,其他会话对相同数据的读写被阻塞
- MDL 锁(元数据锁)不会释放,ALTER TABLE 等操作开始排队
- 连接状态在
SHOW PROCESSLIST中显示为Update或Query,但实际卡在 sleep,无法被连接池回收 - 如果连接池设置了
max-lifetime,该连接可能因超时被池主动 close,但 MySQL 侧仍认为它在运行,导致Lost connection to MySQL server during query报错
为什么连接池“感知不到”这种卡死?
主流连接池(如 HikariCP、Druid)靠心跳或空闲检测来清理连接,但它们只检查连接是否“可 ping 通”,不检查 MySQL 内部是否正在执行 SLEEP()。只要 TCP 连接没断、MySQL 线程没 kill,池就认为连接健康。结果就是:
- 连接池不断分配“看似正常”的连接给业务,实则每个都在内部 sleep
- 连接数缓慢爬升,直到达到
maxPoolSize,新请求直接阻塞在获取连接阶段 - 监控看到的是“连接池耗尽”,但 DBA 查
SHOW PROCESSLIST只看到一堆状态为Update的长时连接,trx_state却是RUNNING,trx_query显示完整 SQL(含SLEEP)
真正危险的是它和长事务叠加后的连锁反应
SLEEP() 本身不是长事务,但它让短事务变成长事务。一旦触发器中还有其他 DML(比如 AFTER INSERT 触发 UPDATE),整个事务窗口就被拉长,undo 日志无法清理、MVCC 读视图长期滞留、磁盘空间悄悄上涨——这些现象单独看都像资源泄漏,但根因就是那一行 SLEEP(0.5)。
最容易被忽略的一点:测试环境往往压测不到真实并发密度,SLEEP() 在单条 SQL 里看起来无害;上线后一旦进入批量导入、日志落库等场景,它就成了压垮连接池的最后一根稻草。











