1.一、lock和latch
mysql
1.二、主键索引和辅助索引
算法
1.三、事务的隔离级别
sql
2.一、主键索引如何加锁
数据库
2.二、辅助索引(非惟一索引)如何加锁
session
1.一、lock和latch
并发
lock::lock的对象是事务,用来锁定的是数据库中的对象,如表,页,行。(这个概念很重要,有助于理解后续的加锁行为)
学习
latch:latch是为了保证并发线程操做临界资源的正确性(本文能够忽略这个概念)线程
1.二、主键索引和辅助索引3d
主键索引(汇集索引/聚簇索引): 每张表按照主键构造一棵b+树,叶子节点存放的即为整张表的行记录数据rest
辅助索引:按辅助索引的键值构建b+树,叶子节点的索引行中,除了包含索引的键值外,还包含一个书签(bookmark),指向主键索引上该行的完整记录
1.三、事务隔离级别
此处再也不赘述,本文会使用mysql默认的隔离级别,REPEATABLE READ(RR)
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 ,发起一个事务,给记录上锁
插入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))右侧,能够插入。