说一下Mysql锁
# 锁类型
- 共享锁(S):锁住一行,阻止其他事物获得相同数据集的排他锁
- 排他锁(X):锁住一行,阻止其他事物获得相同数据集的共享锁和排他锁
为了实现表锁和行锁共存,InnoDB内部还使用了意向锁,这两种意向锁都是表锁。
- 意向共享锁(IS):获得数据行的S锁前要先获得表的IS锁
- 意向排他锁(IX):获得数据行的X锁前要先获得表的IX锁
兼容矩阵
项目 | X | IX | S | IS |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 冲突 | ||
S | 冲突 | 冲突 | ||
IS | 冲突 |
为什么要使用意向锁? 在没有意向锁情况下,事物T1申请了行排他锁,如果事物T2要申请表排他锁,就要遍历表的所有锁看有没有行排他锁,这是耗费性能的。有了意向锁,判断是否有意向排他锁就可以了。
意向共享锁可以多个并存,意向排他锁只能有一个。
在兼容矩阵中,为什么IX和IX是兼容的? 因为IX存在的条件是表中有行拍他锁,IX存在的目的是阻止其他事物申请表排他锁,但是不组织其他事物申请行排他锁。 所以,当表中存在IX锁,在申请行排他锁时,会先申请IX锁并且申请成功 但是如果申请表排他锁则申请失败。
什么SQL加什么锁? 意向锁是InnoDB自动加的,无需用户干预。 update、delete、insert,InnoDB自动加排他锁。 非显式加锁的select,InnoDB不会加锁。
select加锁: 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。
insert加锁: insert会在insert所在行加排他锁,这是一个record lock。 不过,在insert之前会先加一种锁,官方称为insertion intention gap lock,也就是意向gap锁。意向gap锁可以同时存在多个,两个事物可以给相同的gap加意向gap锁,只要插入的记录唯一键不同,两个事物的insert都会成功。 为什么需要意向gap锁? (个人理解)如果没有意向gap锁,insert时需要对比所有未提交的insert记录,看是不是唯一键冲突。有gap锁,则只需要和获得这个gap锁的事务比较唯一键。
# Record lock、gap lock、next-key lock
三种类型的排他锁,锁定数据范围不同: Record lock:只锁一行数据,按唯一键加锁 gap lock:锁住两个索引之间的间隙,但不包含记录本身 只有两种情况才有单纯的gap锁: next-key lock:Record lock+gap lock。
举个例子来看这三种排他锁.如下表,id为PK。
id | name |
---|---|
10 | a |
20 | b |
50 | c |
Record Lock:select * from tab where id = 10 for update; //对id=10单行进行加锁 Gap Lock锁范围:(- ∞,10)(10,20)(20,50)(50,+∞) Next-Key Lock锁范围:(- ∞,10)[10,20)[20,50)[50,+∞)
只有两种场景会有单纯的gap lock:
- update t set name ='d' where id > 60; 锁定 (60,+∞),这里只有gap锁,没有索引本身的锁。
- update t set name ='d' where id < 1; 锁定 (- ∞,1),这里只有gap锁,没有索引本身的锁。
# 锁都加在哪些资源上
- 表
- 索引(主键索引、唯一索引、普通索引)
- 索引之间的gap
为什么任何session都insert不进去? 可能是,表数据少,有一个事物在执行update时全表扫描,把表锁住了。
这种逻辑:先select再update,有什么问题? select是快照读,update是当前读,可能会出现幻读导致数据不一致。 解决办法:
- 乐观锁 update set version=v+1 where version=v
- 悲观锁 select * from t for update
# 4.4 各种条件下的加锁
给定SQL:delete from t1 where id = 10; 那么,当前的事物隔离级别RC、RR。id的索引情况。有如下讨论:
- | RC | RR |
---|---|---|
主键索引 | [外链图片转存中...(img-vkCidSBQ-1653142673824)]只需要将主键上,id = 10的记录加上X锁即可 | [外链图片转存中...(img-IznzsLkw-1653142673825)]只需要将主键上,id = 10的记录加上X锁即可 |
唯一索引 | [外链图片转存中...(img-I8ESnpgb-1653142673825)]此组合中,id是unique索引,而主键是name列。首先会将unique索引上的id=10索引记录加上X锁,同时,会根据读取到的name列,回主键索引(聚簇索引),然后将聚簇索引上的name = ‘d’ 对应的主键索引项加X锁。 | [外链图片转存中...(img-ULIYfRJG-1653142673826)]此组合中,id是unique索引,而主键是name列。首先会将unique索引上的id=10索引记录加上X锁,同时,会根据读取到的name列,回主键索引(聚簇索引),然后将聚簇索引上的name = ‘d’ 对应的主键索引项加X锁。 |
普通索引 | [外链图片转存中...(img-LMDZXlly-1653142673826)]id列索引上,满足id = 10查询条件的记录,均已加锁。同时,这些记录对应的主键索引上的记录也都加上了锁 | [外链图片转存中...(img-KS9pBMEu-1653142673827)]与左图的唯一不同在于:在主键上加了gap锁。 |
非索引 | [外链图片转存中...(img-RmaEu3Nd-1653142673827)]进行全部扫描。聚簇索引上所有的记录,都被加上了X锁 | [外链图片转存中...(img-cffdiN3C-1653142673827)]全表记录加X锁,聚簇索引的每个gap都加gap锁 |