InnoDB关于事务、锁、MVCC专题( 二 )


也就是我们上面说到的在并发情况下会发生的这些问题,
如果并发操作不加控制,不加锁的话,就可能写入了不正确的数据 , 或者导致读取了不正确的数据,破坏了数据的一致性 。因此需要考虑加锁
InnoDB的七种锁共享/排他锁InnoDB呢实现了两种标准的行级锁:共享锁(简称S锁)、排他锁(简称X锁) 。

  • 共享锁:简称为S锁,在事务要读取一条记录时,需要先获取该记录的S锁 。
  • 排他锁:简称X锁 , 在事务需要改动一条记录时 , 需要先获取该记录的X锁 。
如果事务T1持有行R的S锁,那么另一个事务T2请求访问这条记录时,会做如下处理:
  • T2 请求S锁立即被允许,结果 T1和T2都持有R行的S
  • T2 请求X锁不能被立即允许,此操作会阻塞
如果T1持有行R的X锁,那么T2请求R的X、S锁都不能被立即允许,T2 必须等待T1释放X锁才可以,因为X锁与任何的锁都不兼容 。
兼容性 SX S兼容不兼容 X 不兼容 不兼容意向锁意向锁是一种不与行级锁冲突的表级锁 。未来的某个时刻 , 事务可能要加共享或者排它锁时 , 先提前声明一个意向 。注意一下,意向锁,是一个表级别的锁 。
因为InnoDB是支持表锁和行锁共存的,如果一个事务A获取到某一行的排他锁,并未提交,这时候事务B请求获取同一个表的表共享锁 。因为共享锁和排他锁是互斥的,因此事务B想对这个表加共享锁时,需要保证没有其他事务持有这个表的表排他锁,同时还要保证没有其他事务持有表中任意一行的排他锁 。
然后问题来了,你要保证没有其他事务持有表中任意一行的排他锁的话 , 去遍历每一行?这样显然是一个效率很差的做法 。为了解决这个问题 , InnoDB提出了意向锁 。
如果一个事务A获取到某一行的排他锁 , 并未提交,这时候表上就有意向排他锁和这一行的排他锁 。这时候事务B想要获取这个表的共享锁 , 此时因为检测到事务A持有了表的意向排他锁 , 因此事务A必然持有某些行的排他锁 , 也就是说事务B对表的加锁请求需要阻塞等待 , 不再需要去检测表的每一行数据是否存在排他锁啦 。这样效率就高很多啦 。
记录锁记录锁是最简单的行锁 , 仅仅锁住一行 。如: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 。
间隙锁为了解决幻读问题,InnoDB引入了间隙锁(Gap Lock) 。间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙 。它锁住的是一个区间 , 而不仅仅是这个区间中的每一条数据 。
比如lock_mode X locks gap before rec表示X型gap锁 。
临键锁Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁 。说得更具体一点就是:临键锁会封锁索引记录本身,以及索引记录之前的区间,即它的锁区间是前开后闭,比如(5,10]
如果一个会话占有了索引记录R的共享/排他锁,其他会话不能立刻在R之前的区间插入新的索引记录 。
插入意向锁插入意向锁,是插入一行记录操作之前设置的一种间隙锁 。这个锁释放了一种插入方式的信号 。它解决的问题是:多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,就不会阻塞彼此 。
假设有索引值4、7 , 几个不同的事务准备插入5、6,每个锁都在获得插入行的独占锁之前用插入意向锁各自锁住了4、7之间的间隙,但是不阻塞对方因为插入行不冲突
自增锁自增锁是一种特殊的表级别锁 。它是专门针对AUTO_INCREMENT类型的列,对于这种列,如果表中新增数据时就会去持有自增锁 。简言之,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值 。

推荐阅读