概述:
行级锁加锁规则比较复杂,不同场景加锁形式不同,对记录加锁时,加锁的基本单位是next-key lock(左开右闭),其在不同的场景下会退化成间隙锁或者记录锁。通过以下实验来看看不同的场景具体是什么锁。
行级类型主要有三类:
Record Lock:记录锁,只在一条记录上机上锁;
Gap Lock:间隙锁,锁定某一个范围,不包含记录本身,左开右开;
Next-Key Lock:记录锁和间隙锁的组合,包含记录本身,左开右闭;
普通的select语句不会对记录加锁,需要加锁使用下面两种方式:
select ... lock in share mode; //共享锁
select ... for update; //独占锁
实验准备:
创建表:
create table user(
id INT AUTO_INCREMENT,
name VARCHAR(23) NOT NULL,
age INT,
primary key(id)
)engine = INNODB default CHARSET = utf8;
插入一些数据:
唯一索引等值查询
会话1执行:查询一条存在的记录
begin;
select * from user where id = 7 for update;
执行以下语句查看加锁情况:
select * from performance_schema.data_locks\G;
我们重点关注第二行,第一行是因为插入了行级锁所以会生成一个表级别的意向锁。
上面红框是锁的属性主要信息
- LOCK_TYPE:锁类型(RECORD:记录锁)
- LOCK_MODE:X(独占锁),REC_NOT_GAP(记录锁,只锁一行)
- LOCK_DATA:锁的值(主键索引id值为7的一条记录)
因此当查询条件是唯一索引等值查询且记录时存在的,只会锁住要查询的记录一条。
会话1执行:查询一条不存在的记录
begin;
select * from user where id = 8 for update;
- LOCK_TYPE:锁类型(RECORD:记录锁)
- LOCK_MODE:X(独占锁),GAP(间隙锁)
- LOCK_DATA:锁的值(主键索引id值为11)
因为是间隙锁,所以主键索引id的加锁范围是(7,11);
唯一索引范围查找
select * from user_2 where id >= 7 and id <10 for update;
由上图看出在id=7上加了一条记录锁。另外还有范围(7, 11)的间隙锁,所以加锁的范围是[7, 11)。
非唯一索引等值查询
首先给字段age加上索引;
查找的非唯一索引记录存在
select * from user_2 where age = 26 for update;
- 首先给非唯一索引age_index加上next-key lock,范围(25, 26];
- 使用非唯一索引,记录存在。还会加上间隙锁,规则是向下遍历一个区间,间隙锁范围(26,28)
根据以上两点,使用普通索引且记录存在,会加上两个锁,分别是next-key lock(25, 26]和间隙锁(26,28)。
非唯一索引且值不存在
select * from user_2 where age = 23 for update;
非唯一索引不存在时,会在查找的值所在的区间加上间隙锁(20,25);
非唯一索引范围查找
select * from user_2 where age >= 20 and age < 24 for update;
- 开始找的是age = 20,会加上next-key lock(18, 20],因为使用的不是唯一索引,索引不会退化为记录锁。
- 由于是范围查找,向后一个区间走,到age = 25,加上next-key lock(20, 25],使用普通索引不会退化为间隙锁。
综上所述,会产生两个next-key lock (18, 20] 和 (20, 25].
总结
行锁加锁的基本单位是next-key lock,只是会在不同的场景会退化为记录锁或者间隙锁
唯一索引等值查询
- 记录存在,next-key lock退化为记录锁
- 记录不存在,next-key lock退化为间隙锁
非唯一索引等值查询
- 记录存在,除了next-key lock锁外,还会向后一个区间加上间隙锁,一共两把锁;
- 记录不存在,只会加上next-key lock锁,再退化为间隙锁,只有一把锁;
范围查找
- 唯一索引范围查找,如果查找的条件存在,则会有一条记录锁,然后会给后面的范围加上next-key lock(某些条件下会退化为间隙锁);
- 非唯一索引范围查找,next-key lock 不会退化为记录锁或者间隙锁
另外,锁是在遍历索引的时候加上的,并不是针对输出结果加锁。因此当在线上执行update、delete、select…for update等有加锁性质的语句,需要判断语句是否走索引,如果是全表扫描的话,会对每一个索引加next-key lock,等于把整个表锁住了。
就是这事,散会。