Mysql组合索引的update在多种情况下的间隙锁的范围(简单来说)
简单来说,当 UPDATE
语句的 WHERE
条件使用了组合索引,并且需要锁定不存在的“间隙”来防止幻读时,就会产生间隙锁。间隙锁的范围取决于 WHERE
条件如何利用组合索引,以及数据库的隔离级别。
我们用图书馆的例子。比如:
- 分类(Category):计算机、历史、文学
- 出版年份(PublishYear):2020、2021、2022
- 书名(Title):具体书名
现在,图书馆对 (Category, PublishYear, Title)
建立了一个组合索引。这意味着书架上的书是先按分类排好,然后在每个分类里再按出版年份排好,最后在每个年份里按书名排好。
假设你现在是图书馆管理员,要进行一些更新操作。
核心问题: 当你更新某些书时,为了防止别人在你更新期间偷偷“插入”或“删除”一些书,导致你更新完后发现书的数量不对(幻读),你需要锁定一些“空位”(间隙)。这些空位锁定的范围有多大?
深入理解:
间隙锁是 InnoDB 存储引擎在 可重复读(Repeatable Read) 隔离级别下,为了解决 幻读(Phantom Read) 问题而引入的一种锁。它锁定的是索引记录之间的“间隙”,或者索引记录之前/之后的空间。
当 UPDATE
语句使用组合索引时,间隙锁的范围会变得比较复杂,因为它涉及到索引的“最左前缀原则”以及 WHERE
条件的匹配程度。
我们分几种情况来讨论:
情况一:WHERE
条件完全匹配组合索引的“最左前缀”或全部列,且是等值查询。
场景: 你想更新“计算机”分类下,“2021年”出版的,名为“数据库原理”这本书的价格。
UPDATE books SET price = 100 WHERE category = '计算机' AND publish_year = 2021 AND title = '数据库原理';
索引: (Category, PublishYear, Title)
- 数据库会通过组合索引精确找到这本书。
- 由于是精确匹配,并且找到了具体的记录,数据库会给这条记录加上行级排他锁(X Lock)。
- 间隙锁: 在这种情况下,如果
WHERE
条件能够精确匹配到一条或几条存在的记录,并且这些记录是连续的,那么间隙锁的范围通常会非常小,甚至可能没有明显的间隙锁(因为行锁已经足够)。- 为什么? 因为你已经找到了具体的书,你只需要锁住这本书本身。如果其他人在这个位置插入一本完全相同的书(这通常被唯一索引阻止),或者插入一本在排序上紧邻的书,并不会影响你当前对“数据库原理”这本书的更新。
- 例外: 如果
WHERE
条件匹配到了一个不存在的记录,但这个记录可能存在于某个间隙中(例如,你尝试更新一本不存在的书),那么为了防止幻读,可能会在这个不存在的记录应该插入的位置前后加上间隙锁。但对于精确匹配存在的记录,间隙锁通常不是主要考虑。
情况二:WHERE
条件只使用了组合索引的部分列(最左前缀),且是等值查询。
场景: 你想更新“计算机”分类下,所有“2021年”出版的书的价格。
UPDATE books SET price = 100 WHERE category = '计算机' AND publish_year = 2021;
索引: (Category, PublishYear, Title)
- 数据库会通过组合索引找到所有
category = '计算机'
且publish_year = 2021
的书。 - 它会给这些找到的所有行都加上行级排他锁(X Lock)。
- 间隙锁: 这是间隙锁发挥作用的关键场景。为了防止其他事务在“计算机”分类下,“2021年”出版的书籍之间或前后插入新的书籍(例如,插入一本
title = '操作系统'
的书),数据库会在这些行之间以及这些行所在的“范围”前后加上间隙锁。- 范围: 间隙锁会锁定
(Category='计算机', PublishYear=2021)
这个“范围”内的所有可能的Title
值。 - 具体表现: 假设在索引中,
('计算机', 2021, '算法')
后面是('计算机', 2021, '数据结构')
。那么,('计算机', 2021, '算法')
和('计算机', 2021, '数据结构')
之间的间隙会被锁定。同时,('计算机', 2021, '数据结构')
之后,直到下一个('计算机', 2022, ...)
或('历史', ...)
之前的间隙也会被锁定。 - 目的: 确保在你更新期间,没有人能插入新的
('计算机', 2021, ...)
的书,从而避免幻读。
- 范围: 间隙锁会锁定
情况三:WHERE
条件使用了范围查询(BETWEEN
, >
, <
, LIKE
等)。
场景: 你想更新“计算机”分类下,所有“2020年到2022年”出版的书的价格。
UPDATE books SET price = 100 WHERE category = '计算机' AND publish_year BETWEEN 2020 AND 2022;
索引: (Category, PublishYear, Title)
- 数据库会通过组合索引找到所有
category = '计算机'
且publish_year
在 2020 到 2022 之间的书。 - 它会给这些找到的所有行都加上行级排他锁(X Lock)。
- 间隙锁: 间隙锁的范围会覆盖整个查询的范围。
- 起始点: 间隙锁会从
('计算机', 2020, MIN_VALUE)
之前的间隙开始。 - 结束点: 间隙锁会延伸到
('计算机', 2022, MAX_VALUE)
之后的间隙。 - 中间: 在
('计算机', 2020, ...)
到('计算机', 2022, ...)
之间的所有行和间隙都会被锁定。这意味着,即使('计算机', 2021, '操作系统')
这本书不存在,它应该存在的位置也会被间隙锁覆盖,防止其他事务插入。 - 目的: 确保在你更新这个范围内的书时,没有人能插入或删除任何符合这个范围的新书,从而避免幻读。
- 起始点: 间隙锁会从
情况四:WHERE
条件没有使用组合索引的最左前缀,或者没有使用索引。
场景: 你想更新所有“2021年”出版的书的价格(不考虑分类)。
UPDATE books SET price = 100 WHERE publish_year = 2021;
索引: (Category, PublishYear, Title)
- 由于
WHERE
条件没有使用组合索引的最左前缀(Category
),数据库可能无法有效利用这个组合索引。 - 全表扫描: 数据库可能会进行全表扫描来找到所有
publish_year = 2021
的行。 - 锁的升级: 在这种情况下,为了保证事务的隔离性,数据库可能会采取更粗粒度的锁策略:
- 表级锁: 最坏的情况是,数据库为了避免幻读,直接对整个
books
表加表级排他锁(Table-Level X Lock)。这意味着在你的UPDATE
语句执行期间,其他任何对books
表的读写操作都会被阻塞。 - 行锁 + 间隙锁(但范围可能很大): 即使是行锁,由于是全表扫描,它会扫描所有行,并对符合条件的行加行锁。同时,为了防止幻读,它可能需要在整个扫描过程中,对所有扫描到的间隙都加上间隙锁。这实际上等同于锁定了整个表,因为间隙锁会覆盖所有可能的插入点。
- 目的: 确保在全表扫描并更新的过程中,没有新的行被插入或删除,从而避免幻读。
- 表级锁: 最坏的情况是,数据库为了避免幻读,直接对整个
情况五:WHERE
条件使用了组合索引,但条件不精确,导致索引扫描范围很大。
场景: 你想更新所有 category
以“C”开头的书的价格。
UPDATE books SET price = 100 WHERE category LIKE 'C%';
索引: (Category, PublishYear, Title)
- 数据库会利用
Category
上的索引(最左前缀)来查找。 - 它会扫描所有
Category
以 ‘C’ 开头的索引项。 - 间隙锁: 间隙锁的范围会覆盖所有
Category
以 ‘C’ 开头的索引项以及它们之间的所有间隙。- 具体表现: 假设你的索引中有
('计算机', ...)
、('财务', ...)
等。那么从第一个以 ‘C’ 开头的Category
之前,到最后一个以 ‘C’ 开头的Category
之后,所有相关的间隙都会被锁定。 - 目的: 防止其他事务插入新的
Category
以 ‘C’ 开头的书籍,从而避免幻读。
- 具体表现: 假设你的索引中有
总结间隙锁的范围:
间隙锁的范围是动态的,它取决于:
- 隔离级别: 间隙锁主要在 可重复读(Repeatable Read) 隔离级别下生效。在读已提交(Read Committed)隔离级别下,通常不会有间隙锁(除非是外键约束检查等特殊情况)。
WHERE
条件如何利用索引:- 精确匹配(等值查询)到存在记录: 间隙锁范围最小,通常只锁定行本身。
- 范围查询(
BETWEEN
,>
,<
,LIKE
等)或部分索引列匹配: 间隙锁会锁定整个查询范围内的所有行和间隙,以防止幻读。范围越大,间隙锁的范围也越大。 - 无法使用索引或索引效率低下: 可能导致全表扫描,进而可能升级为表级锁,或者间隙锁覆盖整个表,导致并发性急剧下降。
- 索引的类型:
- 唯一索引: 如果
WHERE
条件通过唯一索引精确匹配到一条记录,通常只需要行锁,间隙锁的影响很小。 - 非唯一索引: 非唯一索引更容易产生间隙锁,因为存在多个相同值的可能性,以及在这些相同值之间插入新值的可能性。
- 唯一索引: 如果
核心思想: 间隙锁是为了保护一个“范围”内的“不存在”的数据,防止其他事务在这个范围内插入新的数据,从而破坏当前事务的“可重复读”的幻象。UPDATE
操作在定位到要修改的行后,为了确保这些行在事务提交前不会被其他事务的插入操作所“包围”或“改变其相对位置”,就会对相关的间隙加锁。