mysql出现long semaphore wait的根本原因是长事务、i/o延迟或自适应哈希索引误用,需通过innodb_trx查未提交事务、iostat分析i/o瓶颈、关闭innodb_adaptive_hash_index缓解争用,并确认版本是否存在已知semaphore相关缺陷。

MySQL 出现 long semaphore wait 报错,不是“可能卡”,而是 InnoDB 已经在内部资源争用中濒临死锁或挂起——高负载只是导火索,根因藏在长事务、I/O 延迟或自适应哈希索引误用里。
怎么看是不是长事务在拖后腿
长事务会持续持有行锁、页锁甚至 buffer pool latch,直接阻塞其他线程获取信号量。别只查 SHOW PROCESSLIST 里 Time 大的连接,那只是客户端空闲时间;真正危险的是没提交的事务。
- 执行
SELECT * FROM information_schema.INNODB_TRX\G,重点看TRX_STARTED和TRX_STATE = 'RUNNING'的记录,哪怕TRX_ROWS_LOCKED是 0,也可能占着事务 slot 和 undo 段 - 配合
SELECT * FROM performance_schema.events_statements_current WHERE THREAD_ID IN (SELECT THREAD_ID FROM information_schema.INNODB_TRX)查它最后执行的 SQL,常发现是未提交的BEGIN; INSERT ...;卡在中间 - 如果业务用 ORM(如 Django/SQLAlchemy),检查是否开了 autocommit=False 但忘了
commit()或用了select_for_update()后没释放
磁盘 I/O 瓶颈怎么确认不是背锅侠
信号量等待常被误判为纯 CPU 或锁问题,但底层是 I/O 延迟导致 page read/write 操作卡住,进而让 mtr 提交、buffer pool latch 释放变慢——尤其在 srv/srv0srv.c 或 fil/fil0fil.c 行号报错时更典型。
- 用
iostat -x 1观察%util是否长期 >90%、await是否持续 >20ms(SSD)或 >50ms(HDD);注意不是平均值,而是峰值持续段 - 对比
SHOW ENGINE INNODB STATUS\G中FILE I/O部分的pending normal aio reads/writes数量,若稳定 >10 且不下降,说明 I/O 队列已堆积 - 检查 MySQL 是否启用了
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等待,且集中在建索引、大批量导入后,优先怀疑它 - 运行时关闭:执行
SET GLOBAL innodb_adaptive_hash_index = OFF(8.0.20+ 支持动态关闭,无需重启) - 注意:关掉后 SELECT 性能可能微降,但 INSERT/UPDATE 的稳定性提升明显;若业务读远多于写,再考虑用
innodb_adaptive_hash_index_parts分片缓解
最容易被忽略的细节:版本与补丁差异
MySQL 5.7.19 和 8.0.20 对 semaphore timeout 的处理逻辑不同——前者超 600 秒直接 crash,后者可能尝试 recovery 但 hang 更久。很多线上问题卡在“升级太麻烦”,结果反复复现同一堆栈。
- 5.7.19 的
row0ins.cc line 193和mtr0mtr.cc line 567在 5.7.30+ 已有修复补丁,涉及插入缓冲区 latch 释放时机 - 8.0.20 的
srv/srv0srv.c line 895mutex 竞争,在 8.0.28 后引入了更细粒度的 latch 分区 - 别只盯着配置调优,先确认你用的是否是已知存在 semaphore 相关 crash 的小版本;官方 Release Notes 里搜 “semaphore” 或 “latch hang” 能快速定位











