MySQL锁的类型
这里讲到的MySQL锁和锁的类型都是基于InnoDB来讲的。
共享锁和排他锁
共享锁(shared lock,简称S)允许多个读操作同时进行不相互影响,排他锁(exclusive lock,简称X)会阻塞其它排他锁请求,直到当前释放了锁。
意向锁
意向锁是InnoDB为了支持多个粒度上的锁定提出的概念,可以让行级锁和表级锁共存。
意向锁分为两种,一种是意向共享锁(intension shared lock,简称IS),一种是意向排他锁(intension exclusive lock,简称IX):
- IS表示一个事务要在这个表加S行锁
- IX表示一个事务要在这个表加X行锁
比如select ... lock in share lock会加IS锁,select ... for update会加IX锁。
在一个事务获取一张表S行锁之前,必须对这张表加IS锁或更高强度的锁,在一个事务获取一张表X行锁之前,必须对这张表加IX锁,表级别的锁互斥关系如下所示:
结合下面例子理解互斥关系:
在上面例子里,现开启一个事务加了S行锁,然后对表加X锁,会发现对表加X锁被阻塞了,结合上面表,可以分析在加S行锁的时候,给这张表加了一个IS表锁,这样在加X表锁的时候,因为X表锁和IS表锁是互斥的,就导致加X表锁的操作被阻塞了。
总而言之,意向锁的目的就是为了加行锁的时候,在表锁维度有个标识,表示当前表正在进行一些操作,在加真正的表锁的时候,就可以识别这个标识,判断要加的表锁是否能够加上。
记录锁
记录锁又称为行锁,是InnoDB专门提供的一种锁,上面例子里,select * from MyBooks where id = 0 lock in share mode就会加上一个行锁。
行锁会在增、删、改操作里自动加上,在查询操作,可以显示加上lock in share mode或for update加上。
行锁只会对索引行生效,有几个点需要注意一下:
- 命中的索引必须是主键或者唯一键索引
- 如果SQL语句没有使用索引或者优化器决定不用索引,那么就会锁全表
间隙锁
间隙锁是对索引记录之间、第一个索引记录之前或者最后一个索引记录之后的锁定,无论这个范围内有没有这个值,都会被锁定,间隙可能跨过单个索引值、多个索引值甚至为空。
间隙锁有几个需要注意的点:
- 如果一个条件命中唯一索引,不会使用间隙锁
- 如果明确只查询一条数据,但是没有命中唯一索引,会使用间隙锁锁定该条索引键之前和之后的间隙
- 间隙锁的目的是不让在这个间隙里插入数据,间隙锁之间是不排他的,也就是说可以对同一个间隙加多个间隙锁
- 间隙锁在读提交级别下不生效
假如有表test,有两个字端,字段id是唯一键索引,字段num是普通索引,有数据:
当事务A对3进行加锁时,事务B插入(7, 2),(7, 4)会失败,因为间隙锁把num索引(1, 3),(3, 5)间隙锁住了,这时插入(7, 6)是会成功的。
插入(7, 2)是指插入id=7,num=2。
临键锁
临键锁是记录锁和间隙锁的结合,InnoDB默认会命中临键锁,这时会所这条索引键之前和之后的间隙,同时会锁这条索引键。
在上面的例子下,事务B插入(8, 3)会被阻塞。
记录锁、间隙锁和临键锁之间的关系是什么样的?
丁奇提的两个原则、两个优化和一个Bug有利于理解这三种锁之间的关系:
- 原则1: 加锁的基本单位是临键锁
- 原则2: 查找过程中访问到的对象才会加锁
- 优化1: 索引上的等值查询,给唯一键加锁时,会退化成行锁,注意这里需要有行记录
- 优化2: 索引上的等值查询,向右遍历且最后一个值不满足条件时,退化成间隙锁
- Bug: 唯一索引上的范围查询会访问到不满足条件的第一个值为止
具体可见https://time.geekbang.org/column/intro/139
插入意图锁
插入意图锁的作用是提高插入操作的并发。如果有多个事务的插入操作等待一段间隙被释放,会在等待排他锁时加上插入意图锁,等到间隙锁被释放时,这多个插入操作不会相互阻塞。
自增锁
自增锁是个表级别的锁,专门处理插入操作自增列,一个事务在执行插入操作时,别的事务的插入操作会阻塞,知道这个插入操作执行完毕,这样这个插入操作的自增列的数据是连续的。