长事务未提交会持续占用buffer pool latch和undo slot,导致其他线程在btr0sea.cc或buf0buf.c上等待;常见于orm中autocommit=false后未commit或select_for_update()异常未释放;需查innodb_trx中trx_state='running'且trx_started久、trx_rows_locked高的事务,并结合events_statements_current定位最后执行sql。

长事务没提交,信号量被卡死
不是“可能卡”,是 INNODB_TRX 里那些 TRX_STATE = 'RUNNING' 却迟迟不 COMMIT 的事务,正在持续占用 buffer pool latch 和 undo slot,其他线程一碰就等在 btr0sea.cc 或 buf0buf.c 上。ORM(比如 Django)里 autocommit=False 后忘了 commit(),或者用了 select_for_update() 但异常路径没释放,最常见。
查法:直接跑 SELECT * FROM information_schema.INNODB_TRX\G,重点看 TRX_STARTED 时间戳和 TRX_ROWS_LOCKED;再用 SELECT * FROM performance_schema.events_statements_current WHERE THREAD_ID IN (SELECT THREAD_ID FROM information_schema.INNODB_TRX) 抓它最后执行的 SQL——往往就是卡在半截的 INSERT 或 UPDATE。
高并发写入触发页分裂+锁争用
多个线程往同一张表、甚至同一主键范围(比如自增 ID 插入热点)猛插,row0ins.cc line 2997 就会密集报等信号量,本质是 B+ 树页分裂时对页 latch 的抢夺。尤其当 fill factor 接近 100%、又没主键或主键冲突时,争用更剧烈。
- 检查表是否缺少主键,或主键设计导致插入点集中(如全用 UUID 前缀相同)
- 观察
SHOW ENGINE INNODB STATUS\G中LATEST DETECTED DEADLOCK和SEMAPHORES段,若等待线程堆栈反复出现在row0ins.cc或btr0cur.cc,基本锁定是写入争用 - 临时缓解可加
innodb_autoinc_lock_mode=2(无间隙锁模式),但需确认 binlog 格式兼容
磁盘 I/O 慢拖垮整个信号量链
信号量等得久,常被当成锁问题,其实是底层 I/O 没跟上:page read/write 卡住 → mtr 提交延迟 → buffer pool latch 释放变慢 → 更多线程排队等信号量。错误日志里若出现 srv/srv0srv.c 或 fil/fil0fil.c 行号,基本就是 I/O 瓶颈。
验证方式:
-
iostat -x 1看%util是否长期 >90%,await是否 SSD >20ms / HDD >50ms -
SHOW ENGINE INNODB STATUS\G查FILE I/O部分的pending normal aio reads/writes,稳定 >10 就说明队列已堆积 - 如果启用了
innodb_use_native_aio=ON(Linux 默认),但内核或驱动不兼容,反而引发 aio hang;可试设为OFF观察是否改善
innodb_adaptive_hash_index 在写多场景反成瓶颈
这个功能本意加速等值查询,但在大批量 INSERT/UPDATE 时,哈希表频繁重建会激烈争抢 btr_search_latch,多个线程卡在 btr0sea.cc,最终触发 semaphore wait。这不是 bug,是设计权衡。
判断依据:
- 错误日志中多线程堆栈集中在
btr0sea.cc - 问题总在批量导入、建索引后爆发
- MySQL 8.0.20+ 支持运行时关闭:
SET GLOBAL innodb_adaptive_hash_index = OFF
关掉它不会影响读性能太多,但能立刻缓解写入高峰下的信号量争用——这点容易被忽略,因为很多人只盯着锁和事务,忘了哈希索引本身也是个共享资源。











