两万字详解InnoDB的锁( 二 )


  • T2 请求S锁立即被允许,结果 T1和T2都持有R行的S锁
  • T2 请求X锁不能被立即允许,此操作阻塞
如果T1持有行R的X锁,那么T2请求R的X、S锁都不能被立即允许,T2 必须等待T1释放X锁才可以,因为X锁与任何的锁都不兼容 。
S锁和X锁的兼容关系如下:
两万字详解InnoDB的锁

文章插图
 
X锁和S锁是对于行记录来说的话,可以称它们为行级锁或者行锁 。我们认为行锁的粒度就比较细,其实一个事务也可以在表级别下加锁,我们称之为表锁 。给表加的锁,也是可以分为X锁和S锁的哈 。
如果一个事务给表已经加了S锁,则:
  • 别的事务可以继续获得该表的S锁,也可以获得该表中某些记录的S锁 。
  • 别的事务不可以继续获得该表的X锁,也不可以获得该表中某些记录的X锁 。
如果一个事务给表加了X锁,那么
  • 别的事务不可以获得该表的S锁,也不可以获得该表某些记录的S锁 。
  • 别的事务不可以获得该表的X锁,也不可以继续获得该表某些记录的X锁 。
2.2 意向锁什么是意向锁呢?意向锁是一种不与行级锁冲突的表级锁 。未来的某个时刻,事务可能要加共享或者排它锁时,先提前声明一个意向 。注意一下,意向锁,是一个表级别的锁哈 。
为什么需要意向锁呢? 或者换个通俗的说法,为什么要加共享锁或排他锁时的时候,需要提前声明个意向锁呢呢?
因为InnoDB是支持表锁和行锁共存的,如果一个事务A获取到某一行的排他锁,并未提交,这时候事务B请求获取同一个表的表共享锁 。因为共享锁和排他锁是互斥的,因此事务B想对这个表加共享锁时,需要保证没有其他事务持有这个表的表排他锁,同时还要保证没有其他事务持有表中任意一行的排他锁 。
然后问题来了,你要保证没有其他事务持有表中任意一行的排他锁的话,去遍历每一行?这样显然是一个效率很差的做法 。为了解决这个问题,InnoDb的设计大叔提出了意向锁 。
意向锁是如何解决这个问题的呢? 我们来看下
意向锁分为两类:
  • 意向共享锁:简称IS锁,当事务准备在某些记录上加S锁时,需要现在表级别加一个IS锁 。
  • 意向排他锁:简称IX锁,当事务准备在某条记录上加上X锁时,需要现在表级别加一个IX锁 。
比如:
  • select ... lock in share mode,要给表设置IS锁;
  • select ... for update,要给表设置IX锁;
意向锁又是如何解决这个效率低的问题呢:
如果一个事务A获取到某一行的排他锁,并未提交,这时候表上就有意向排他锁和这一行的排他锁 。这时候事务B想要获取这个表的共享锁,此时因为检测到事务A持有了表的意向排他锁,因此事务A必然持有某些行的排他锁,也就是说事务B对表的加锁请求需要阻塞等待,不再需要去检测表的每一行数据是否存在排他锁啦 。
意向锁仅仅表明意向的锁,意向锁之间不会互斥,是可以并行的,整体兼容性如下:
两万字详解InnoDB的锁

文章插图
 
2.3 记录锁(Record Lock)记录锁是最简单的行锁,仅仅锁住一行 。如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE,如果C1字段是主键或者是唯一索引的话,这个SQL会加一个记录锁(Record Lock)
记录锁永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁 。它会阻塞其他事务对这行记录的插入、更新、删除 。
一般我们看死锁日志时,都是找关键词,比如lock_mode X locks rec but not gap),就表示一个X型的记录锁 。记录锁的关键词就是rec but not gap 。以下就是一个记录锁的日志:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` trx id 10078 lock_mode X locks rec but not gapRecord lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 8000000a; asc;; 1: len 6; hex 00000000274f; asc'O;; 2: len 7; hex b60000019d0110; asc;;2.4 间隙锁(Gap Lock)为了解决幻读问题,InnoDB引入了间隙锁(Gap Lock) 。间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙 。它锁住的是一个区间,而不仅仅是这个区间中的每一条数据 。


推荐阅读