InnoDB的行锁,原来为你做了这么多( 二 )


InnoDB的行锁,原来为你做了这么多

文章插图
 
锁的关系
记录锁所谓记录锁 Record Locks , 就是锁住确定的一行行记录 。它分为共享锁和排它锁 。分别对应不同的SQL写法 。
共享锁共享锁 Shared Locks  , 简称S锁 。使用以下SQL可能触发:
SELECT ... LOCK IN SHARE MODE之所以说“可能”触发 , 是因为它查到了数据库有确定的记录才会锁住这些记录 , 否则会变成间隙锁 。这个其实很好理解 , 找到了数据 , 才锁它 。如果没找到数据 , 就锁这个间隙 。
排他锁排他锁 Exclusive locks  , 简称X锁 。使用一下SQL可能触发:
【InnoDB的行锁,原来为你做了这么多】SELECT ... FOR UPDATE这里的“可能”含义与上面同理 , 不赘述 。
间隙锁间隙锁 Gap - Lock , 顾名思义 , 锁住一个间隙 。上文我们提到过 , InnoDB默认的隔离级别是RR , 但是通过间隙锁来一定程度上的解决了幻读的问题 。它是怎么解决的呢?就是通过间隙锁来解决的 。
上面两种SQL , 如果没有查找到确定的记录 , 就会根据条件去锁住一个间隙 。间隙锁是根据已有数据的一个左开右闭的区间 。
还是这个案例数据(假设数据都是从1开始):
id(主键) c(普通索引) d(无索引) 5 5 5 10 10 10 15 15 15 20 20 20 25 25 25
对于下面这些区间的操作 , 会有对应的间隙锁:(0, 5], (5, 10], (10, 15], (15, 20], (20, 25], (25, 正无穷) 。
什么意思呢?假如你的SQL查询的范围不同 , 那它锁住的区间就不同 。比如:
-- 锁住(0, 5]SELECT * FROM demo where id = 3;-- 锁住(10, 15]SELECT * FROM demo where id = 11;间隙锁其实是“共享”的 。也就是说 , 多个事务可以获取同一个区间的间隙锁 。
InnoDB的行锁,原来为你做了这么多

文章插图
 
间隙锁不互相阻塞
插入意向锁插入意向锁 Insert Intention Locks , 代表当前事务准备插入一行数据 。使用INSERT/UPDATE/DELETE等语句会获得插入意向锁 。
「插入意向锁和插入意向锁之间是兼容的 , 只要插入的键值不同 , 就不会相互阻塞」 。比如以下两个SQL , 在不同的事务中 , 哪怕它们在同一个间隙 , 只要没有间隙锁 , 就不会阻塞:
InnoDB的行锁,原来为你做了这么多

文章插图
 
插入两条不同的记录不阻塞
但如果两个事务插入同一个key , 那就会阻塞 。
InnoDB的行锁,原来为你做了这么多

文章插图
 
两个插入意向锁阻塞的情况
插入意向锁可以保证两个事务插入key不同的数据的时候不冲突 , 提升并发性 。
「但是间隙锁会阻塞插入意向锁」!这也可以理解 , 因为InnoDB想在RR隔离级别就解决幻读问题 。所以A事务用SELECT语句获取了一个间隙锁 , 自然不希望B事务在这个期间往这个间隙插入一条新的记录 。
InnoDB的行锁,原来为你做了这么多

文章插图
 
间隙锁阻塞插入意向锁
与索引的关系不管哪种行级锁 , 「行级锁的其实都是索引」 。所以在上面的demo中 , 如果对id(主键)或者column c(普通索引)操作 , 都会触发相应的行级锁 , 但如果对column d(无索引)做同样的操作 , InnoDB就会对表中所有数据加锁 , 实际效果跟表级锁一样 。
所以一定要注意 , 如果要上锁 , 需要注意是否走了索引 , 不要弄成了表级锁造成安全事故 。
一个死锁案例最后给一个关于间隙锁和插入意向锁的死锁案例吧 , 也是之前在项目上遇到过的真实案例 。
过程