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


自增锁是一个表级别锁,那为什么会话A事务还没结束,事务会话B可以执行插入成功呢?不是应该锁表嘛?
这是因为在参数innodb_autoinc_lock_mode上,这个参数设置为1的时候,相当于将这种auto_inc lock弱化为了一个更轻量级的互斥自增长机制去实现,官方称之为mutex
不同事务RR和RC下加锁的规则

  1. 在RC(读已提交) 的隔离级别下 , 对查询条件是主键id的场景 , 会加一个排他锁(X锁) , 或者说加一个X型的记录锁 。
  2. 在RC(读已提交) 的隔离级别下 , 对查询条件是唯一索引的场景 , 该SQL需要加两个X锁,一个对应于 唯一索引上的记录,另一把锁对应于聚簇索引上(主键索引) 。
为什么主键索引上的记录也要加锁呢?
如果并发的一个SQL,是通过主键索引来,此时 , 如果delete语句没有将主键索引上的记录加锁,那么并发的update就会感知不到delete语句的存在,违背了同一记录上的更新/删除需要串行执行的约束 。
3. 在RC(读已提交) 的隔离级别下,对查询条件是普通索引的场景,那么对应的所有满足SQL查询条件的记录,都会加上锁 。同时,这些记录对应主键索引,也会上锁
4. 在RC(读已提交) 的隔离级别下,对查询条件是无索引(只是一个常规的列)的场景,MySQL会走聚簇索引进行全表扫描过滤 。每条记录都会加上X锁 。但是,为了效率考虑,MySQL在这方面进行了改进,在扫描过程中,若记录不满足过滤条件,会进行解锁操作
5. 在RR(可重复读)的隔离级别下,对查询条件是主键id的场景 , 会加一个排他锁(X锁),或者说加一个X型的记录锁 。和RC是一样的
6. 在RR(可重复读)的隔离级别下 , 对查询条件是唯一索引的场景,该SQL需要加两个X锁,一个对应于 唯一索引上的记录,另一把锁对应于聚簇索引上(主键索引) 。和RC是一样的
7. 在RR(可重复读)的隔离级别下,对查询条件是普通索引的场景 , 除了会加X锁 , 还会加间隙Gap锁 。Gap锁的提出,是为了解决幻读问题引入的,它是一种加在两个索引之间的锁 。
8. 在RR(可重复读)的隔离级别下,对查询条件是无索引的场景,查询条件列没有索引,主键索引的所有记录,都将加上X锁,每条记录间也都加上间隙Gap锁 。任何加锁并发的SQL,都是不能执行的,全表都是锁死的状态 。
RR隔离级别下加锁规则两个原则两个优化和一个bug
  • 原则1:加锁的基本单位都是next-key locknext-key lock(临键锁)是前开后闭区间 。
  • 原则2:查找过程中访问到的对象才会加锁 。
  • 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁(Record lock)
  • 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁(Gap lock) 。
  • 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止 。
limit 语句减少加锁范围
总结
  • 如果查询没有命中索引,则退化为表锁;
  • 如果等值查询唯一索引且命中唯一一条记录 , 则退化为行锁;
  • 如果等值查询唯一索引且没有命中记录,则退化为临近结点的间隙锁;
  • 如果等值查询非唯一索引且没有命中记录,退化为临近结点的间隙锁(包括结点也被锁定);如果命中记录,则锁定所有命中行的临键锁,并同时锁定最大记录行下一个区间的间隙锁 。
  • 如果范围查询唯一索引或查询非唯一索引且命中记录,则锁定所有命中行的临键锁 ,并同时锁定最大记录行下一个区间的间隙锁 。
  • 如果范围查询索引且没有命中记录,退化为临近结点的间隙锁(包括结点也被锁定) 。
MVCC我们知道排他锁与任何锁互斥 , 一旦写数据的任务没有完成,数据是不能被其他任务读取的,这对并发度有较大的影响 。
有没有可能,进一步提高并发呢?
即使写任务没有完成,其他读任务也可能并发,这就引出了数据多版本 。
数据多版本是一种能够进一步提高并发的方法,它的核心原理是:
(1)写任务发生时,将数据克隆一份,以版本号区分;
(2)写任务操作新克隆的数据,直至提交;

推荐阅读