长事务未提交会持续占用undo slot、行锁和buffer pool latch,导致其他线程在信号量上阻塞;应通过innodb_trx查trx_started超30分钟的running事务,并结合events_statements_current定位最后执行sql,而非依赖processlist的time或sleep状态。

长事务没提交,信号量被死死占着
不是“连接多”,而是某些事务从早上就没提交,它持续持有 undo slot、行锁、甚至 buffer pool latch,其他线程一靠近就卡在信号量上。你看到 SHOW PROCESSLIST 里状态是 Sleep,但 INNODB_TRX 里 TRX_STATE = 'RUNNING',这就是真凶。
查法很简单:
- 执行
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STARTED ,揪出老而不死的事务 - 用
SELECT * FROM performance_schema.events_statements_current WHERE THREAD_ID = [trx_mysql_thread_id]看它最后干了啥——常是 ORM 开了autocommit=False后忘了commit() - 别信
Time字段,那只是客户端空闲时间;TRX_STARTED才是真相
Buffer Pool 实例数设成 1,所有线程抢同一把锁
MySQL 5.7 默认在 innodb_buffer_pool_size > 1G 时设 innodb_buffer_pool_instances = 8,但如果你手动写死为 1,哪怕 buffer pool 是 32G,所有线程还是挤在 buf0buf.cc 的全局 buf_pool_mutex 上排队。
怎么确认是它?看 SHOW ENGINE INNODB STATUS\G 的 SEMAPHORES 段:如果大量线程卡在 buf0buf.cc 行号,且 RW-excl spins 和 OS waits 高得离谱,基本就是这问题。
调法注意:
- 先查当前值:
SELECT @@innodb_buffer_pool_instances - 再算最小粒度:
128MB × instances(innodb_buffer_pool_chunk_size默认 128MB) - 设
SET GLOBAL innodb_buffer_pool_instances = 8后,必须同步调大innodb_buffer_pool_size到能被整除,否则会向上取整爆内存
自适应哈希索引(AHI)在批量写入时反成瓶颈
innodb_adaptive_hash_index 本意加速等值查询,但在高并发 INSERT/UPDATE 场景下,多个线程争抢 btr_search_latch 重建哈希表,最终全堵在 btr0sea.cc ——错误日志里反复出现这个文件名,就是明确信号。
它不是 bug,是设计权衡。临时验证是否是它:
- 运行时关闭:
SET GLOBAL innodb_adaptive_hash_index = OFF(MySQL 5.7.20+ 支持) - 观察
SHOW ENGINE INNODB STATUS\G中SEMAPHORES段等待线程是否从btr0sea.cc消失 - 若有效,可写进配置文件永久关闭;但别一刀切,读多写少场景它仍有益
磁盘 I/O 延迟导致 page read/write 卡住底层信号量
信号量等待常被误判为 CPU 或锁问题,实际根源可能是 I/O 慢:page 读不进来、log 写不出去,mtr 提交卡住,buffer pool latch 就一直释放不了。
关键证据链:
-
iostat -x 1看await是否持续 >50ms(HDD)或 >20ms(SSD),%util是否长期 >90% -
SHOW ENGINE INNODB STATUS\G的FILE I/O段里pending normal aio reads/writes稳定 >10 且不降 - 错误日志报错位置含
fil/fil0fil.c或srv/srv0srv.c,基本可锁定 I/O 层 - 检查
innodb_use_native_aio是否开启但内核/驱动不兼容,可临时设为OFF测试
真正麻烦的是这四类问题经常叠加出现:一个长事务拖住 buffer pool,又触发 AHI 重建争抢,I/O 还跟不上,信号量等待就指数级放大。排查时别只盯日志里最先报错的那一行,得顺着 SEMAPHORES 段的线程堆栈和文件行号,一层层往底层挖。











