上篇MySQL系列有提到间隙锁,但是我感觉没有讲清楚,仅仅只是介绍了一下间隙锁,所以再来记录一下间隙锁。
上篇传送门:juejin.cn/post/698622…
先说一下结论: 读,分为当前读和快照读,MVCC解决的是快照读的幻读问题,而当前读的幻读问题需要Gap锁+行锁来解决。
Innodb支持三种行锁定方式:
行锁:锁的是索引,如果SQL没有走索引,那么会全表扫描,从而升级为表锁。
如果有主键,Innodb会把主键作为聚簇索引。
如果没有主键,Innodb会选择第一个不包含有 NULL 值的唯一索引作为主键索引
如果没有主键且没有唯一索引,Innodb会选择内置的rowId(Innodb内部对每一行数据维护了一个递增的rowId)作为聚簇索引。
PS: 想必肯定有同学会想,如果某张表没有索引怎么办?
Gap锁(间隙锁):当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时(增删改查操作,Mysql会默认加锁),InnoDB会给符合条件的已有数据记录的索引项加行锁。对于键值在条件范围内但并不存在的记录加上间隙锁。Innodb 为了解决幻读问题时引入的锁机制,所以只有在 Read Repeatable 、Serializable 隔离级别才有。要取消间隙锁的话,切换隔离级别为读已提交即可。
PS: 如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!
Next-Key Lock:行锁与间隙锁组合起来用就叫做Next-Key Lock。
快照读: 就是普通的select语句,不包含for update的和lock in share mode的select语句。
当前读是通过MVCC机制(uodo_log + readView) 来实现的, 上篇有讲juejin.cn/post/698622…
当前读:读取的是最新的数据,并且需要先获取对应记录的锁,包含以下这些 SQL 类型:
select ... lock in share mode
select ... for update
增删改(MySQL默认加锁)
当前读是通过Next-Key Lock(行锁+间隙锁) 来实现的
下面来看个例子:
数据库的原始数据如下图
在RR(可重复读)隔离级别下开启两个事物进行操作:
咦。是不是有点奇怪,SQL1是快照读,SQL4是当前读,RR隔离级别下,在同一个事务中还是发生了幻读。
当时我就想了一下,不是说MVCC解决了快照读的幻读问题,间隙锁解决了当前读的问题吗。为什么我这里还是会出现幻读的问题呢?
是这样的,MySQL的当前读,是指在一个事务中的第一个SQL就是当前读了,也就是说第一个SQL就会加锁,其他的事务拿不到锁,根本没办法对对应的记录增删改。必须要等第一个事务释放锁之后,其他的事务才能对对应的记录增删改查。虽然解决了当前读的幻读问题,但是性能受到了影响。
建立一个user表的记录如下:
事务A执行delete from user where age=3
, 而age=3位于age(2,7)之间, 所以MySQL对age(2,7)区间的数据加了间隙锁,当事务B对age(2,7)区间进行增删改操作时,会发生阻塞的现象。如图:
再看个例子,原始数据如下:
同理,当事务A执行update user set money=100 where id>=5
时,MySQL会对id【5, 正无穷大)进行加锁,这时,事务B只要在id【5, 正无穷大)区间发生增删改操作,都会发生阻塞。因为对id=5的这一行数据加了行锁,并且对id>5的但还不存在的数据加了间隙锁。如图:
历史数据如图:
开启两个事务进行操作:
发生死锁的原因:
age=5的数据不存在,即对age(2,7)加上了间隙锁。
age=8的数据不存在,即对age(7,9)加上了间隙锁。
增删改查语句,MySQL会默认加锁,SQL3插入的是age=8, 而age(7,9)已经被加上间隙锁,所以SQL3阻塞,等待age(7,9)的间隙锁释放。
增删改查语句,MySQL会默认加锁,SQL3插入的是age=5,而age(2,7)已经被加上间隙锁,所以SQL4死锁,等待age(2,7)的间隙锁释放。从而发生死锁。
作者:Boom
链接:https://juejin.cn/post/6988887904651051044
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。