找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

355

积分

0

好友

47

主题
发表于 6 小时前 | 查看: 1| 回复: 0

有一种在数据库领域被广泛流传的说法:锁作用在索引上。这个说法对吗?我们不妨思考一下。

顺着这个思路,可以先做一个初步的逻辑梳理:

  • 磁盘上的数据是以“页”为单位被加载到内存中的。
  • 数据库引擎(如InnoDB)会基于主键构建聚簇索引。
  • 假设操作A执行 UPDATE table SET ... WHERE id = 64
  • 操作A通过索引结构快速定位到ID=64的记录所在的数据页。
  • 更新这条记录时,将对应的索引条目锁住。
  • 此时,操作B也想通过相同的索引路径 (WHERE id = 64) 来更新同一条记录。
  • 操作B发现索引条目已被锁住,于是被成功阻塞。

这样理解似乎天衣无缝,符合直觉。

B+树索引从单页到多层的演变过程示意图

那么,如果 WHERE 条件使用的是无索引字段呢?例如 email = 'example@qq.com'。众所周知,这将导致全表扫描。

如果“锁作用在索引上”这个前提成立,就会出现一个漏洞:你操作A通过索引路径找到了记录并锁住了索引,我操作C不走索引,直接全表扫描。我绕过了索引检索,直接找到数据记录,岂不是就跳过了你的锁,也能成功执行UPDATE?

一个表达惊讶和疑问的卡通人物表情包

...等等,这么明显的漏洞轮得到我来发现吗?如果这个漏洞不存在,那么“锁作用在索引上”的说法可能就是错的。或者,是我们对它的理解有偏差?

让我们再次审视聚簇索引的结构。以一个查询 WHERE id = 22 为例:

  1. 首先找到根页(假设是#40),分析ID范围,检索到id=20索引记录,它指向下一个索引页#55
  2. 找到索引页#55,分析ID范围,检索到id=20索引记录,它指向数据页#90
  3. 找到数据页#90,最终检索到数据记录id=22

现在问题来了:锁到底加在哪了?

  • 如果锁加在根页#40id=20这条索引记录上,那么整个id在20~39区间的记录都会被锁住。
  • 如果锁加在索引页#55id=20这条索引记录上,那么整个id在20~24区间的记录都会被锁住。

这显然不是我们所说的行级锁。看来,之前的理解确实可能有问题。

根据以上分析,锁很可能最终是作用在聚簇索引的叶子节点——也就是真实数据记录所在的数据页上。具体来说,是锁在了数据页#90的记录id=22上。

这说明,“锁作用在索引上”这句话里的“索引”,指的并非那些指向其他页面的“索引记录”

完整的多层B+树索引结构示意图,包含根页、索引页和数据页

一个技术讨论,差点变成了文字游戏。现在我们可以得出更精确的结论:

“锁作用在索引上”这一说法,容易引发一个普遍的误解:锁是加在一个抽象的、与数据分离的“索引条目”上。

更准确的说法是:“锁最终作用在聚簇索引的叶子节点真实记录所在的页”上。

InnoDB的行级锁,是通过对索引记录加锁来实现的。这里的“索引记录”,对于聚簇索引而言,即为其叶子节点存储的真实数据行;对于二级索引而言,为其叶子节点存储的索引键值及主键。为了保证数据一致性,任何通过二级索引的加锁操作,最终都必须在对应的聚簇索引叶子节点记录上追加一把锁。

那么,现在回看最初那个“漏洞”问题:

  • 操作C执行 UPDATE ... WHERE email = 'example@qq.com'
  • 它通过全表扫描方式检索,即直接顺序扫描聚簇索引的所有叶子节点(数据页)。
  • 操作A已经对id=22的记录加了锁(锁在数据页#90的记录L上)。
  • 当操作C扫描到数据页#90的记录L时,会发现该记录已被锁住,从而被阻塞。
  • 漏洞消失了,逻辑自洽了。

所以,对MySQL锁机制的理解,关键在于认识到InnoDB中数据和(聚簇)索引是一体的。锁,归根结底是加在构成这棵索引树的、存储着真实数据的“数据页记录”上。希望本文的探讨能帮助你更清晰地理解这一核心机制。更多深入的技术讨论,欢迎访问云栈社区进行交流。




上一篇:MySQL关键字使用指南与数据类型选择实战建议
下一篇:卡尔曼滤波器原理与Python实践:从传感器噪声中平滑估计状态
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 16:28 , Processed in 0.220964 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表