CPU没满磁盘没满TPS上不去——B-tree索引锁才是真凶

举报
这个DBA有点耶 发表于 2026/10/15 16:38:56 2026/10/15
【摘要】 写密集、高并发场景下,MySQL吞吐量上不去,加CPU、换SSD都没用——瓶颈可能不在硬件,在InnoDB的B-tree索引锁上。B-tree的结构修改操作(SMO)一次只能跑一个,索引级锁把所有写操作串行化。本文拆解B-tree索引锁的瓶颈根源、并发SMO的技术原理、Latch Coupling的加锁优化机制,以及热点行事务合并的实践思路。

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

你有没有遇到过这种情况:系统写入压力一大,QPS就上不去了。CPU没跑满,磁盘IO也还行,内存也够,但TPS就是卡在一个数字上,加机器、换SSD都不管用。

问题可能不在硬件上。在InnoDB的B-tree索引锁上。

今天把这个写密集场景的隐形瓶颈彻底拆开讲清楚。

先搞懂几个词:

Latch:InnoDB内部的短期轻量级锁,用于保护内存数据结构。不同于事务锁(Lock),Latch不遵循2PL协议,持有时间极短,在访问结束后立即释放。

SMO(Structure Modification Operation) :B-tree的结构修改操作。当插入导致节点分裂、删除导致节点合并时,需要修改多个物理页的结构。

Latch Coupling:B-tree遍历的加锁策略。在从父节点移动到子节点时,先获取子节点的Latch,再释放父节点的Latch,保证遍历过程中最多同时持有两个节点的Latch。

热点行:同一行记录被大量并发事务频繁更新,导致严重的行锁等待。

一、B-tree索引锁的瓶颈根源

InnoDB用B-tree组织索引,每个物理页(B-tree节点)有一个读写锁。对单个节点加锁,并不能保证多线程访问B-tree时的读写一致性——因为一个SMO操作会改动多个物理页。

为了解决这个问题,InnoDB采用了“同时持有多个节点锁”的策略,并设计了加锁规则来避免死锁。但这带来了两个瓶颈。

瓶颈一:SMO无法并发。

同一时刻只允许一个SMO执行。索引级锁把所有结构变更串行化了。写密集场景下,频繁的插入触发的节点分裂被一把全局锁挡住,吞吐量撞上天花板。这是写入性能最根本的结构性瓶颈之一。

瓶颈二:加锁范围大。

为了避免死锁,乐观操作要持有遍历路径上所有节点的共享锁(S锁),悲观操作要持有所有可能修改节点的排他锁(X锁)。并发越高,锁与锁之间的竞争越激烈,尤其在一些关键节点上,竞争会高度集中。

二、并发SMO的技术原理

解决SMO串行化的核心思路是:把冲突从索引级别下沉到页面级别。

传统方式用一把全局索引锁串行化所有结构变更。优化后,允许所有操作并发访问B-tree,每个物理页有自己的锁。冲突只在真正竞争同一页面时才会发生——绝大部分操作各自访问不同页面,互不干扰。

一个直观的类比:传统方式像一条单车道,所有车必须排队通过。优化后的方式像一条多车道,每辆车只占用自己所在的车道,只有真正需要并道时才互相等待。

并发SMO的实现需要解决两个核心问题:

问题一:如何保证B-tree结构变更的原子性。 一个SMO可能涉及父节点、子节点、兄弟节点三个页面的修改。如果两个SMO同时操作相邻的页面,可能产生结构不一致。解决方案是对SMO涉及的页面按固定顺序加锁,避免死锁,同时保证结构变更的原子性。

问题二:如何避免遍历路径上的锁冲突。 B-tree遍历是从根节点到叶子节点的路径。如果遍历过程中一直持有根节点的锁,所有并发操作都会被阻塞。解决方案是Latch Coupling——在移动到子节点时释放父节点的锁。

三、Latch Coupling:缩小加锁范围的核心机制

Latch Coupling是B-tree并发控制的关键技术。它的核心思想是:在遍历B-tree时,最多同时持有两个节点的Latch。

工作流程:

  1. 持有根节点的Latch

  2. 找到目标子节点,获取子节点的Latch

  3. 释放父节点的Latch——这是关键步骤

  4. 继续向下遍历,重复步骤2和3

这样,遍历过程中同时持有的Latch数量从“整条路径”减少到“最多两个节点”。

效果:大幅减少了锁的持有数量和竞争持续时间。根节点不再成为所有并发操作的瓶颈——操作在向下遍历的过程中迅速释放上层节点的锁,让其他并发操作可以进入。

四、热点行的事务合并

B-tree索引锁优化解决的是“写入量太大、B-tree频繁分裂”的问题。另一类写密集瓶颈是热点行锁等待——秒杀、账户入账、计数器这类场景,大量并发事务同时更新同一行记录。

传统行锁机制下,后一个事务必须等前一个事务提交并释放锁之后才能加锁。TPS极低、延迟极高。

事务合并的核心思路是把并发到来的热点更新事务自动组织成一个group。group内的leader负责对热点行加锁,其余follower无需等待锁释放即可执行热点更新。

执行流程:

  1. 并发事务自动组group,第一个进入的是leader,其余是follower

  2. leader加锁并执行热点行更新

  3. leader完成后立即grant下一个follower执行,无需等待leader提交

  4. follower被唤醒后,直接读取最新数据执行更新,跳过加锁步骤

  5. leader在事务结束时释放行锁,唤醒下一个leader

由于follower不再等待leader提交,事务等待锁的时间大幅缩短,在高延迟场景下提升效果尤为显著。

五、小结

MySQL在写密集高并发场景下的吞吐量瓶颈,往往不在硬件,在InnoDB的B-tree索引锁上。SMO无法并发、加锁范围大,这两个结构性问题把写操作串行化了。并发SMO把冲突从索引级别下沉到页面级别,Latch Coupling把遍历过程中的锁持有量从“整条路径”减少到“最多两个节点”,热点行的事务合并则从行锁层面减少锁等待。理解这些机制,才能判断你的瓶颈到底在“索引”还是“行”。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。