前言
本文主要介绍InnoDB的锁,主要是什么了解锁的种类,已经为后续研究InnoDB事务打好基础。
环境:
操作系统:macos
数据库版本:mysql 8.0.23 innodb引擎
本文所有内容都是基于以上环境的,如果有异议的地方欢迎邮件交流。
锁分类
InnoDB主要有以下这几种锁:
- 共享锁和独占锁(Shared and Exclusive Locks)
- 意向锁(Intention Locks)
- 记录锁(Record Locks)
- 间隙锁(Gap Locks)
- 临键锁(Next-Key Locks等于行锁+间隙锁)
- 插入意向锁(Insert Intention Locks)
- 自增锁(AUTO-INC Locks)
- 空间索引的Predicate Locks (Predicate Locks for Spatial Indexes)
可以参考下图:
共享锁和独占锁
都是InnoDB标准行级锁的实现,InnoDB执行行级锁定的方式是,在搜索或扫描表索引时,它会对遇到的索引记录设置共享或排他锁。一次,行锁实际上是索引记录锁。
- 共享锁(S锁):允许持有锁的事务读取一行,又叫读锁
- 独占锁(X锁):允许持有锁的事务更新或者删除一行,又叫写锁
如果,事务T1持有第r行记录的共享锁,那么事务T2对第r行尝试加一个共享锁能够立即执行,即T1和T2都能保持共享锁;如果,事务T2尝试持有r的独占锁,不能被立即执行(必须等T1的共享锁解锁)。
如果,事物T1持有第r行记录的独占锁,那么事务T2对r上任意一种类型的锁的请求都不能立即被授予。
案例:
下面所有案例都是手动提交事务
共享锁:
##第一次验证共享锁-共享锁
begin ;
select * from user where id = 1 for share ;
##执行完上面语句,在打开一个事务
begin ;
select * from user where id = 1 for share ;
###现象:上面都很快查出来
##验证共享锁-独占锁
begin ;
select * from user where id = 1 for share ;
##执行完上面语句,在打开一个事务
begin ;
select * from user where id = 1 for share ;
###现象:上面执行第二个查询的时候,持续一段时间并报错
独占锁:
##验证独占锁-独占锁
begin ;
select * from user where id = 1 for update ;
##执行完上面语句,在打开一个事务
begin ;
update user set name=1 where id=1;
##现象:上面执行第二个查询的时候,持续一段时间并报错
##验证独占锁-共享锁
begin ;
select * from user where id = 1 for update ;
##执行完上面语句,在打开一个事务
begin ;
select * from user where id = 1 for share ;
###现象:上面执行第二个查询的时候,持续一段时间并报错
意向锁
是表锁,不会锁表,只是一个标记,目的是加速事务中是否有行级锁的判定,具体介绍如下:
mysql中有表锁
LOCK TABLES mytable READ;
用读锁锁表,会阻塞其他事务修改表数据
LOCK TABLES mytable WRITE;
用写锁锁表,会阻塞其他事务读和写
InnoDB支持行级锁
如上面所述,行锁分为X
和S
锁。
意向锁来源
当表级锁和行级锁并存时,假如事务A用行锁S锁
锁住表中的一行,那么这一行只能读不能写。
之后,事务B申请表锁的X
锁,如果B申请成功,理论上可以修改表中任意一行,与A的行锁冲突;那么只能让B不能申请成功,必须先判断表中的每一行是否已被行锁X
锁住,要扫描整个表。如果说没有行锁呢?是不是就白白的扫了一次整表,这样效率不高。
因此,意向锁诞生了。
在意向锁存在情况下,事务A必须先申请表的意向共享锁IS
,成功后再申请一行的行锁(S);在意向锁存在的情况下,事务B在申请表锁X
锁的时候,会受到共享意向锁IS
锁的阻塞,就不必在扫一次全表就可以判断能不能申请成功了。
意向锁的加锁策略
- 在事务获得表中一行的共享锁之前,它必须首先获得表上的IS锁或更强的锁
- 在事务获得表中一行的排他锁之前,它必须首先获得表上的IX锁
意向锁兼容性
X | IX | S | IS | |
---|---|---|---|---|
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Conflict | Compatible | Compatible |
如上表,这个是官网的表格,看完会有个问题为什么IX和X会冲突,那么任意两个写操做,即使不同行也会死锁,其实官方文档说的很清楚,上述表格是表级锁类型的兼容Table-level lock type compatibility is summarized in the following matrix
。所以,不要和上面的行级锁的X锁弄混。
记录锁(Record Locks)
记录锁是索引上的锁,总是锁定索引记录而不是实际数据。
如果没有创建索引,InnoDB会创建一个隐藏的聚集索引,并用这个索引来锁定记录,具体可以参考之前InnoDB索引详解的文章
例如:
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
会在索引c1=10上开锁,防止其他事物增、删、改
间隙锁(Gap Locks)
间隙锁是对索引记录之间间隙的锁,或者是第一个索引记录的之前的,或最后一个索引记录之后,作用是防止间隔中被其他记录插入
栗子:
select * from user where id between 11 an 20
,在id属性上加间隙锁,那么间隙锁的范围为:
(-∞,11),
(10,20),
(11,+∞),
以下条件都会产生间隙锁(RR事务隔离级别下):
- 使用普通索引
- 使用多列唯一索引
- 使用唯一索引锁定多条记录
对于使用唯一索引锁定行以搜索唯一行语句,不需要间隙锁;如果没有索引或者有一个非唯一索引,语句会锁定前面的间隙,例如:
select * from user where id=3
,如果id上加了唯一索引,就只会产生记录锁,不会产生间隙锁;如果,id上没有索引或者不是唯一索引,那么会加上间隙锁,锁定(-∞,3)这个间隙
临键锁(Next-Key Locks)
next-key locks
是索引记录上记录锁和间隙锁的组合,作用是防止幻读(配合mvcc机制)。
假设索引包含值10、11、13和20。此索引可能的next-key锁包括以下时间间隔,其中圆括号表示不包含时间间隔端点,方括号表示包含端点:
(-∞,10],
(10,11],
(11,13],
(13,20],
(20,+∞],
插入意向锁(Insert Intention Locks)
插入意向锁是插入操作之前,由插入操作设置的一种间隙锁。此锁表示插入的意图,即如果插入到同一索引间隙中的多个事务没有插入到间隙中的同一位置,则它们不需要等待彼此。假设存在值为4和7的索引记录。分别尝试插入值5和6的单独事务,在获得插入行的独占锁之前,每个事务都使用插入意图锁锁定4和7之间的间隙,但不会相互阻止,因为这些行不冲突。
以上,就是InnoDB常用的锁,大部分参考的是官方文档mysql锁。有不同见解的,可以随时邮件交流!