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


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)写任务操作新克隆的数据,直至提交;
(3)并发读任务可以继续读取旧版本的数据,不至于阻塞;
MVCC 实现的原理大致:
InnoDB 每一行数据都有一个隐藏的回滚指针,用于指向该行修改前的最后一个历史版本,这个历史版本存放在 undo log 中 。如果要执行更新操作,会将原记录放入 undo log 中,并通过隐藏的回滚指针指向 undo log 中的原记录 。其它事务此时需要查询时,就是查询 undo log 中这行数据的最后一个历史版本 。MVCC 最大的好处是读不加锁,读写不冲突,极大地增加了 MySQL 的并发性 。通过 MVCC,保证了事务 ACID 中的 I(隔离性)特性 。
redo、undo为什么要有redo日志?
redo log 指事务中操作的任何数据,将最新的数据备份到一个地方
数据库事务提交后,必须将更新后的数据刷到磁盘上,以保证ACID特性 。磁盘随机写性能较低,如果每次都刷盘,会极大影响数据库的吞吐量 。优化方式是,将修改行为先写到redo日志里(此时变成了顺序写),再定期将数据刷到磁盘上,这样能极大提高性能
redo log 不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入 redo 中 。具体的落盘策略可以进行配置。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启 MySQL 服务的时候,根据 redo log 进行重做,从而达到事务的未入磁盘数据进行持久化这一特性 。

经验总结扩展阅读