参考 https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
本文尽量排除不必要的细节,只看锁和锁实现的功能,文章基于MySQL8.0的官方文档
以下内容描述基于可重复度事务隔离级别(Repeatable Read)
宏观上来看锁分表锁和行锁
当你把这些锁搞明白是干嘛的了, MySQL的事务隔离基本就能搞明白了
0. 预备知识
- 为什么要有锁?
锁是保证 并发 和 数据一致性 的权衡的产物
,也就是说有了锁,既保证了并发的能力,又保证了数据一致性 脏读
: 读到了别的事务回滚前的脏数据不可重复读
: 同一个事务中对同一行数据读取两次返回结果不一样幻读
: 同一个事务中范围查询,第一次读N条,第二次读到N+M条- 锁可以解决
脏读
,不可重复读
,脏读
的问题,维护了事务的特性 快照读
是不会上锁的当前读
会上锁,当前读有两种
-
select ... lock in share mode
会尝试获取共享锁
-
select ... for update
会尝试获取排它锁
update
,insert
,delete
都会尝试获取排它锁
排它锁
和共享锁
是啥,看下文
1. 行级锁
这两种是标准的行锁(行级共享锁和排他锁)
1.1. 共享锁Shared Locks
(S
)
- 用来给指定的行加锁,读取
- 加锁方式:
select ... lock in share mode
- 多个
共享锁
,锁同一个行是不会互相冲突的,所以叫共享锁
1.2. 排他锁Exclusive Locks
(X
)
- 加锁方式:
insert
,update
,delete
,select ... for update
1.3 共享锁和排它锁的冲突情况
- 一下是在同一行加锁的情况
- T1加上了
共享锁 S
,T2可以加共享锁 S
但不能加排它锁 X
- T1加上了
排它锁 X
,T2不可以加共享锁 S
和排它锁 X
- 加不上锁,就只能被阻塞,等待T1释放锁
1.4 共享锁
和排他锁
用途
主要是解决脏读,不可重复读和幻读的问题
共享锁和排他锁加锁有一下几种情况(锁再同一个行row上面):
- 如果T1已经加了
S Lock
,那么T2在同一行上面可以加S Lock
- 如果T1已经加了
S Lock
,那么T2在同一行上面不可以加X Lock
,T2被阻塞 - 如果T1已经加了
X Lock
,那么T2在同一行上面不能加S Lock
或X Lock
,T2被阻塞,T2只能等待T1释放锁
2. 表级意向锁
Intention locks are table-level locks that indicate which type of lock (shared or exclusive) a transaction requires later for a row in a table.
- 意向锁是表级别的锁, 它的存在是为了体现出是不是有某个事务对这个表上了行锁
- 意向锁的主要目的是为了展示出,有某个事务正在锁住这个表的某行,节省了有些情况下其他事务直接去查找行锁的时间
2.1. 意向共享锁 Intention Shared Locks
(IS
)
- 如果一个表有
IS
锁,则表明,现在有一个事务已经获得或者将要获得共享锁 Shared Locks
SELECT ... FOR SHARE
可以设置S
锁, 同时数据库帮我们触发IS
(老版本是:select ... lock in share mode
)- 如果一个事务想要获取
共享锁 S
,则必须先获取意向共享锁 IS
2.2. 意向排他锁 Intention Exclusive Locks
(IX
)
- 如果一个表有
IX
锁,则表明,现在有一个事务已经获得或者将要获得排他锁 Exclusive Locks
SELECT ... FOR UPDATE
可以设置X
锁, 同时数据库帮我们触发IX
- 如果一个事务想要获取
排他锁 X
,则必须先获取意向排他锁 IX
3. 行锁和表锁的冲突和兼容
- X:
行级排它锁
- IX:
表级排它锁
- S:
行级共享锁
- IS :
表级共享锁
如果新的事务将要获取的锁 和已有的事务发生冲突,则一直阻塞到已有的事务释放锁
具体各个锁之间的关系看下表:
↓(T1)↓ \ →(T2)→ | X | IX | S | IS |
---|---|---|---|---|
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Compatible | Compatible | Compatible |
4 行锁的实现方式
实现方式有: 记录锁,间隙锁,临键锁
他们与共享锁,排他锁相比是另外一种维度
也就是说,记录锁既可以是共享锁,又可以是排他锁,就看你怎么用
4.1 记录锁(Record Locks
)
- 记录锁是锁在索引上的,锁的是具体的某个值,这个值正好在索引上
- 如果表没有索引会被设置在聚簇索引上
- 当where字句,精确匹配到索引上的某个值
例:select a from t where a = 10
,这里就会阻止insert
,update
,delete
对a=10这行的操作
(注:同时还会在10左右创造两个间隙锁,实际上RR隔离级别创造的是临键锁,看3.3的介绍)
4.2 间隙锁(Gap Locks
)
- 间隙锁是把锁加在两个索引值之间,比如索引只有两个值
1
,10
,那么间隙锁就可能会在(1,10)
,(infimum,1)
,(10,supremum)
这几个开区间上加锁(infimum是最小值,supremum 是最大值) - 间隙可以横跨多个索引的值,假如有索引值
1
,5
,10
,select * from t where a between 2 and 8 for update
,就会锁住(1,5)
和(5,10)
这2个开区间 - 间隙锁会在扫描索引的时候,把条件所指范围扫到的
间隙 gap
都锁上,也就是说,可能会锁上多个间隙 - 既然这些间隙被锁住了,那么想要在这些间隙上插入值,肯定会被阻塞的
- 注意: 如果是唯一索引精确查询,则只会对查到的那一行加记录锁,不会加间隙锁
例:
- 设a有三个值
1,10,20
,a字段上有索引 select a from t where a = 10 for update
会创造两个间隙锁``(1,10)
,(10,20)
和一个记录锁 a = 10
select a from t where a = 15 for update
会创造一个间隙锁 (10,20)
- (另: 如果where b = 10,b上没有索引,我发现全表都被锁住了,具体原理我还没看)
4.3 临键锁(Next-key Locks
)
临键锁
实际上就是记录锁
和间隙锁
的组合- where条件所指范围,只要扫描到的
记录锁
和间隙锁
都组合起来,这个锁就叫临键锁
临键锁
解决了幻读的问题,这样只要第一次读取的where条件范围内都被锁住,那么第二次读取,也就能保证和第一次读取一致了.- 官方描述幻读问题
4.4 插入意向锁(Insert Intention Locks
)
- 插入意向锁实际上是一种特别的间隙锁
- 当insert操作发生时,会尝试在指定索引的间隙上加一个这个锁
- 这个锁有个特点, 如果两个插入操作都加在同一个
间隙gap
上,而这个间隙gap
之前没有其他种类的间隙锁,这两个插入操作,会都会成功设置插入意向锁
,且不会互相阻塞的. - 插入意向锁解决了一个并发插入的问题,如果多个事务在同一个间隙上只发生插入操作,大家都去等待一个
排它锁
那么效率会很低,而如果使用插入意向锁
则多个插入事务可以同时并发进行
4.5 自增锁(AUTO-INC Locks
)
自增锁
是一种特殊的表锁
- 也就是说所有的insert操作都会排队等待这个自增锁来获取自增值
- 但是这个锁的只会在insert操作的时候存在,与事务无关
- 一旦insert操作结束,锁就会释放
自增锁
设计目的是为了保证自增值获取的顺序性- 一般这个自增锁是不会影响性能的,所以不用太多考虑
- 具体的自增算法看官网,这里不赘述
4.6 空间索引上的谓词锁
略
5. 总结
MySQL这么多锁,实际上就是在为一个目的服务: 在保证数据一致性的前提下,提升并发性能