MySQL 8.0锁管理器通过拆分全局mutex为多细粒度锁(rec_hash/prdt_hash分片锁、wait_mutex等),显著缓解CPU核间争用;死锁检测改为持续追踪等待图,P99延迟降60%+;新增NOWAIT/SKIP LOCKED语法实现应用层锁行为主动控制。

锁管理器从单 mutex 拆成多细粒度锁,缓解 CPU 核间争用
MySQL 5.7 的 lock_sys->mutex 是全局一把大锁,所有事务申请行锁、释放锁、检测死锁都得排队抢这一个资源。在 32 核服务器上,大量线程同时调用 lock_rec_lock() 或 lock_table_lock() 时,CPU 缓存行频繁失效(false sharing),导致大量核间同步开销——你看到的不是“慢”,是大量线程在 spin-wait 或 futex 等待中空转。
8.0 把它拆成:lock_sys->mutex(仅管哈希表元数据)、lock_sys->wait_mutex(管等待图维护)、rec_hash 和 prdt_hash 各自带独立分片锁。这意味着:热点行锁操作和死锁检测不再互锁,不同哈希桶上的行锁操作可并行执行。
- 实测中,当
innodb_sync_array_size保持默认 1(即 1 个 sync array)时,rec_hash分片仍只有 1 个,性能提升有限;建议设为 4 或 8(需配合物理核数) - 若应用存在明显热点主键更新(如计数器表),且
performance_schema.data_locks中某几个LOCK_DATA值反复高频出现,说明哈希分片不均,调大innodb_sync_array_size能立竿见影 - 旧监控脚本若依赖
SHOW ENGINE INNODB STATUS解析LOCK WAIT区块,会因输出格式变更直接失败——8.0 改用结构化字段(如WAITING TRANSACTION,HOLDING LOCK)输出,必须重写解析逻辑
死锁检测从“按需扫描”变成“持续追踪”,降低锁等待抖动
5.7 的死锁检测是懒加载:只有事务卡在 innodb_lock_wait_timeout 边缘时,才触发一次全图遍历。结果就是——你看到的锁等待时间忽高忽低,有时 10ms 解决,有时卡满 50s 才回滚。
8.0 默认开启 innodb_deadlock_detect=ON,后台有专用线程持续维护等待图(wait-for graph)。只要形成环,毫秒内就选 victim 回滚。这不是“更快”,而是“更稳”:锁等待时间方差大幅收窄,P99 延迟可降 60%+(实测场景:订单状态机高频 UPDATE)。
- 代价是 CPU 使用率小幅上升(约 +3%~5%,取决于并发事务数),但换来的是确定性响应——这对支付、库存类业务至关重要
- 若你确认业务逻辑几乎不可能死锁(比如所有 UPDATE 都按固定字段顺序执行),可关掉它:
SET GLOBAL innodb_deadlock_detect=OFF,再把innodb_lock_wait_timeout调小到 10~30 秒兜底 - 注意:关闭后,
INFORMATION_SCHEMA.INNODB_TRX中的TRX_WAIT_LOCK_ID字段可能为空,别靠它判断阻塞链
MDL 锁升级更早、更严格,减少“DDL 卡住全部查询”的雪崩
5.7 的 MDL 锁允许 DML 插队:一个长事务开着,ALTER TABLE 会一直等,期间新来的 SELECT/INSERT 全被堵在 MDL 共享锁获取阶段,形成“一卡全卡”。8.0 强制 DDL 在第一步就申请 MDL_EXCLUSIVE,如果发现已有活跃事务持有 MDL_SHARED,立刻报错或超时,不排队。
这看似“更严”,实则让问题暴露得更早、更可控。DBA 能快速定位是谁没提交事务,而不是查半天才发现是某个凌晨遗留的未关闭连接。
- 检查当前阻塞源:
SELECT * FROM performance_schema.metadata_locks WHERE LOCK_STATUS = 'PENDING' -
FLUSH TABLES WITH READ LOCK在 8.0 下直接被拒绝,备份脚本里还留着这句,会失败——改用mysqldump --single-transaction或mysqlpump - 临时表也参与 MDL 管理,高频创建/删除临时表的应用(如报表引擎),要注意
metadata_locks_cache_size是否够用,否则会触发频繁内存分配
NOWAIT 和 SKIP LOCKED 让应用层能主动控制锁行为
以前遇到行锁冲突,应用只能干等或靠 innodb_lock_wait_timeout 被动中断。8.0 提供两个 SQL 级控制开关,把锁决策权交还给业务逻辑。
SELECT ... FOR UPDATE NOWAIT 遇锁立即报错 ERROR 1205 (40001): Deadlock found when trying to get lock; try restarting transaction(实际是锁等待超时错误码,但语义明确);SELECT ... FOR UPDATE SKIP LOCKED 则跳过已锁定行,常用于任务队列消费,避免多个 worker 争同一行。
- 订单分发场景典型写法:
SELECT * FROM orders WHERE status = 'pending' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED - 不要滥用
NOWAIT做轮询——它会把锁冲突压力转移到应用层重试逻辑,应配合指数退避 -
SKIP LOCKED不影响一致性,但注意它只跳过当前语句可见的已锁行;若事务中后续还有其它锁操作,仍可能触发死锁
真正难调的不是参数本身,是理解哪些锁瓶颈其实在应用层——比如一个没加索引的 UPDATE ... WHERE status='processing',在 8.0 上照样会锁全表,此时调 innodb_sync_array_size 毫无意义。先看 EXPLAIN,再看 data_locks,最后动内核参数。



















