浅谈Innodb的锁实现

目录

1、知识准备

1.一、lock和latch
mysql

1.二、主键索引和辅助索引
算法

1.三、事务的隔离级别
sql

2、Innodb行锁的三种算法

2.一、主键索引如何加锁
数据库

2.二、辅助索引(非惟一索引)如何加锁
session

3、发生死锁的几种状况

4、我的小结


1、知识准备

1.一、lock和latch
并发

lock::lock的对象是事务,用来锁定的是数据库中的对象,如表,页,行。(这个概念很重要,有助于理解后续的加锁行为
学习

latch:latch是为了保证并发线程操做临界资源的正确性(本文能够忽略这个概念)线程

1.二、主键索引和辅助索引3d

主键索引(汇集索引/聚簇索引): 每张表按照主键构造一棵b+树,叶子节点存放的即为整张表的行记录数据rest

辅助索引:按辅助索引的键值构建b+树,叶子节点的索引行中,除了包含索引的键值外,还包含一个书签(bookmark),指向主键索引上该行的完整记录

1.三、事务隔离级别

此处再也不赘述,本文会使用mysql默认的隔离级别,REPEATABLE READ(RR)

2、Innodb行锁的三种算法

Innodb有三种行锁的算法,分别是:

1⃣️、Record Lock:单个行记录的锁

2⃣️、Gap Lock:间隙锁,锁定一个范围,但不包含记录自己

3⃣️、Next-Key Lock:Record Lock + Gap Lock

下面咱们分析下,不一样场景下的加锁状况,以及为何要用对应的加锁算法。

2.一、主键索引如何加锁(mysql默认事务隔离级别:RR)

delete from t where id = 7; 这种状况加锁很简单,因为主键惟一,因此只须要在主键上id=7加X锁便可。(Record Lock)


                                  图1-1

2.二、辅助索引(非惟一索引)如何加锁(mysql默认事务隔离级别:RR)

select * from t where id=5 for update; 该场景下的加锁以下图所示。

                                                      图1-2

从图中能够看出,本文除了在辅助索引和对应的主键索引上加了两个行记录的X锁(Record Lock)外,还在辅助索引上加了3个GAP锁。那么为何要用GAP锁呢?其实这个是Innodb为了解决幻读问题(phantom problem)。你们想一下,为了保证一个事务中的连续两次读(如:select * from t where id=5 for update)结果相同,那么就要防止上锁期间,有id=5的记录插入表中,因为id是非惟一索引,考虑到b+树索引的连续性,因此,可以插入id=5记录的,只有(3,5),(5,5),(5,9)三个区间,即上图三个红箭头的位置范围(上文1.1中提到lock的对象是数据库中的对象,因此GAP锁的的边界会加在具体的索引记录上,因此id=5的话,那就会在3和9之间加上GAP锁。)

网上有些资料,包括我看的一些书上,对于Next-Key Lock的描述要么模凌两可,要么浅尝则止。这样的描述很容易误导刚刚入坑的同窗。大部分的资料描述的区间锁范围都是左闭右开。好比上文中select * from t where id=5 for update;那么GAP LOCK的范围就是 (3,5],(5,5],(5,9]。按照这个描述,那么id=3的记录不可插入,id=9的记录能够插入。那么,事实是否是这样的呢?

下面咱们一块儿来尝试一下,直接上图:

首先咱们看下表中的记录(上文中的例子):


step1: sessionA ,发起一个事务,给记录上锁


setp2: sessionB,发起一个事务,插入边界值。

插入id=3的两条语句,一条失败了,一条成功了,状况好像跟咱们想的有点不同。。。疑问 。 继续上图


插入id=9的两条语句,又是一条失败了,一条成功了。这个状况,和Next-Key Lock的定义,左闭右开,貌似不同啊委屈

下面,咱们一块儿来分析下,究竟是为何?

要想搞明白这个问题,仍是得提到1.1章节中,加粗的那个lock的定义,它是加在索引上的。再结合索引的连续性,那么这个问题就好理解了大笑

请你们把目光回到图1-2,对于辅助索引叶子节点上的排序,能够简化为该图中的样式。因此,GAP LOCK的范围是((3,d) ,(5,c)),((5,c) ,(5,f)),((5,f) ,(9,g)),RECORD LOCK锁定的是(5,c)和(5,f)。因此对于边界值的插入就很清楚了哈

场景一、

mysql> insert into t(id,name) values(3,'f');

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

(3,f) 在((3,d) ,(5,c))之间,不能插入。

场景二、

mysql> insert into t(id,name) values(3,'b');

Query OK, 1 row affected (0.00 sec)

(3,b) 在((3,d) ,(5,c))的左侧,能够插入。

场景三、

mysql> insert into t(id,name) values(9,'e');

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

(9,e) 在((5,f) ,(9,g))之间,不能插入。

场景四、

mysql> insert into t(id,name) values(9,'h');

Query OK, 1 row affected (0.00 sec)

(9,h) 在((5,f) ,(9,g))右侧,能够插入。


3、发生死锁的几种状况

3.一、显示的互相等待(简单场景)

3.一、隐式的互相等待(稍复杂场景)
还记得图1-2中的场景吗?辅助索引上加锁后,会在对应的主键索引上加锁。那么当一张表中有多个辅助索引的时候,是否是会初现死锁呢?这个场景做为思考题,你们本身思考下哈 大笑

4、我的小结

本章的分享,其实就是我学习这块知识遇到问题,以及解决问题的过程。想要理解innodb的加锁原理,必需要理解b+树和索引。固然了,遇到难题,不要轻易放弃哈,有时候用下逆向思惟,想下做者发明这个技术,是为了解决什么问题 大笑
相关文章
相关标签/搜索