InnoDB——锁的算法

InnoDB存储引擎使用RecordLock,GapLock和Next-KeyLock三种行锁算法,防止幻读问题。Next-KeyLock是GapLock和RecordLock的组合,锁定范围并防止PhantomProblem。在唯一索引等值查询时,Next-KeyLock可能降级为RecordLock,提高并发性。在非唯一索引或部分唯一索引查询时,仍使用Next-KeyLock以防止幻读。默认的REPEATABLEREAD隔离级别下,InnoDB利用Next-KeyLocking防止幻读,而在READCOMMITTED级别下则使用RecordLock。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

行锁的3种算法

InnoDB存储引擎有三种行锁的算法,分别是:

  • Record Lock:单个行记录上的锁。
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
  • Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身

Record Lock总是会去锁住索引记录,如果InnoDB存储引擎在表建立时没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定

Next-Key Lock

Next-Key Lock是结合了Gap Lock和Record Lock的一种锁算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法
例如一个索引有10,11,13,20这四个值,那么该索引可能被Next-Key Locking的区间为:

(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +♾️)

采用Next-Key Lock的锁定技术称为Next-Key Locking。其设计目的时为了解决Phantom Problem(幻读)。
这种锁定技术锁定的不是单个值,而是一个范围,是谓词锁(predict lock)的一种改进。

previous-key locking

除了next-key locking还有previous-key locking技。同样上述的索引10、11、13、20,若采用previous-key locking技术,那么可锁定的范围区间为:

(-∞, 10)
[10, 11)
[11, 13)
[13, 20)
[20, +♾️)

Next-Key Lock降级为Record Lock

若事务T1已经通过next-key locking锁定了如下范围:
(10, 11]、(11, 13]
当插入新的记录12时,则锁定的范围会变成:
(10, 11]、(11, 12]、(12, 13]

然而,当对唯一索引的 等值 查询时,InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock仅锁住索引本身,而不是范围

有下面这样一个表:

CREATE TABLE t (a INT PRIMARY KEY);
INSERT INTO t SELECT 1;
INSERT INTO t SELECt 2;
INSERT INTO t SELECt 5;

然后有这样的执行语句:

时间会话A会话B
1BEGIN;
2SELECT * FROM t
WHERE a = 5 FOR UPDATE;
3BEGIN;
4INSERT INTO t SELECT 4;
5COMMIT; #成功,不需要等待锁
6COMMIT;

在上面语句中,会话A中首先对a=5进行X锁定,且由于a是主键且唯一,因此锁定的仅是5这个值,而不会再锁定(2, 5)这个间隙(锁),否则会导致插入记录4会阻塞。因此会话B插入记录4不会阻塞,成功后立即返回。
这就是Next-Key Lock算法降级成Record Lock的优化,提高了应用的并发性

辅助索引下Next-Key Lock 不降级

首先创建下面这个表z:

CREATE TABLE z (a INT, b INT, PRIMARY KEY(a), KEY(b));
INSERT INTO z SELECT 1, 1;
INSERT INTO z SELECT 3, 1;
INSERT INTO z SELECT 5, 3;
INSERT INTO z SELECT 7, 6;
INSERT INTO z SELECT 10, 8;

表z的列b是辅助索引,若在会话A中执行下面的SQL语句:

SELECT * FROM Z WHERE b=3 FOR UPDATE;

这时SQL语句通过索引列b进行查询,因此会使用传统的Next-Key Locking技术加锁。
并且由于有两个索引列,需要分别对其进行锁定

  • 对于**聚集索引,仅对列a=5(主键)**的索引加上Record Lock。
  • 而对于辅助索引,会对其加上Next-Key Lock,锁定的范围是**(1, 3]。特别注意,InnoDB还会对辅助索引下一个键值加上gap lock,即还有一个辅助索引范围为(3, 6)的锁**。

因此,下面的新会话B中的SQL语句在阻塞:

SELECT * FROM z WHERE a=5 LOCK IN SHARE MODE;
INSERT INTO z SELECT 4, 2;
INSERT INTO z SELECT 6, 5;
  • 第一个语句无法执行,它select了主键为5的记录,但a=5(主键)的列已经在上面会话A中被锁定了(Record Lock)
  • 第二句SQL,插入主键4,这没问题。但插入的辅助索引的值为2,在锁定的范围(1,3)中,因此也会被苏泽
  • 第三句同理,因为5在(3,6)的范围也会被阻塞。

从上面的例子可以看出,Gap Lock的作用是为了阻止多个事务将记录插入到同一范围内,而这回导致Phantom Problem(幻读)问题产生。

假如上面例子中,会话A锁定了b=3的记录,若没有Next-Key Lock锁住(1, 3],那么用户同时可以插入索引b列为3的记录,这会导致会话A中再次执行同样的查询会返回不同的记录,即导致Phantorm Problem(幻读)的产生

最后需要提醒的是,对于唯一键值的锁定,Next-Key Lock降级为Record Lock仅存在于查询所有的唯一索引列。若唯一索引由多个列组成,而查询仅是查找多个唯一索引列的其中一个,那么查询其实是range类型查询,而不是point类型查询,则InnoDB依然使用Next-Key Lock进行锁定。

解决Phantom Problem(幻读)

在默认的事务隔离级别(REPEATABLE READ)下,InnoDB采用Next-Key Locking机制来避免幻读。

这点可能不同于Oracle,它可能需要在SERIALIZABLE的事务隔离级别下才能解决幻读。

Phantom Problem(幻读)是指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次SQL语句可能会返回之前不存在的行

InnoDB采用Next-Key Locking的算法避免幻读。
对于SQL语句 SELECT * FROM t WHERE a > 2 FOR UPDATE (a列上有索引),其锁住的不是5单个值,而是对(2,+♾️)这个范围加了X锁。因此任何对于这个范围的插入都是不允许的,从而避免幻读。

InnoDB默认的事务隔离级别是REPEATABLE READ,在该隔离级别下,其采用Next-Key Locking的方式来加锁而在事务隔离界别READ COMMIT下,仅采用Record Lock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值