『MySQL』跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现( 二 )


如果这时候有事务C希望获得行r的排它锁 , 那么就必须等待事务A和事务B释放行r的共享锁之后 , 才能获得排它锁 , 这种叫做锁不兼容 。
『MySQL』跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现
本文插图
普通的select不会对行上锁 , 而select…lock in share mode会上共享锁 , select…for update会上排它锁 。

  • 对于普通的select的读取方式 , 称为”快照读“ , 也叫”一致性非锁定读“ 。
  • 对于带锁的select读取 , 或者update tb set a = a+1(读取a的当前值) , 称为“当前读” , 也叫“一致性锁定读” 。
如果在update、insert的时候 , 不能进行select , 那么服务的并发访问性能就太差了 。 因此 , 我们日常的查询 , 都是“快照读” , 不会上锁 , 只有在update\insert\“当前读”的时候 , 才会上锁 。 而为了解决“快照读”的并发访问问题 , 就引入了MVCC 。
2.2 多版本并发控制MVCC 如果说上面的行锁是一种悲观锁 , 那么MVCC就是一种乐观锁的实现方式 , 而且是一种很常用的乐观锁实现方式 。
所谓多版本 , 就是一行记录在数据库中存储了多个版本 , 每个版本以事务ID作为版本号 。 InnoDB 里面每个事务有一个唯一的事务 ID , 是在事务开始的时候向InnoDB的事务系统申请的 , 并且按照申请顺序严格递增的 。 假如一行记录被多个事务更新 , 那么 , 就会产生多个版本的记录 。
以某一行数据作为例子:
『MySQL』跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现
本文插图
经过两次事务的操作 , value从22变成了19 , 同时 , 保留了三个事务id , 15、25、30 。
在每个记录多版本的基础上 , 需要利用“一致性视图” , 来做版本的可见性判断 。
这里 , 我们要区分MySQL里面的两个”视图”概念:
  • 一个是view , 通过语法create view … 实现 , 主要创建一个虚拟表 , 用来执行查询语句 。
  • 一个是InnoDB用来实现mvcc的一致性视图(consistent read view) , 纯逻辑概念 , 没有物理结构 , 定义了在事务期间 , 你能看到哪些版本的数据 。
我们全文提到的“视图”都是第二种 , 主要是支持InnoDB在“读已提交”和“可重复读”级别的并发访问问题 。
  • “读未提及”级别下 , 没有一致性视图
  • “读已提交”级别下 , 会在 每个SQL开始执行的时候 创建一致性视图
  • “可重复读”级别下 , 会在 每个事务开始的时候 创建一致性视图
  • “串行化”级别下 , 直接通过加锁避免并发问题
下面 , 我们简单介绍一下创建一致性视图的逻辑 。
以“可重复读”级别为例 。
  • 当一个事务开启的时候 , 会向系统申请一个新事务id
  • 此时 , 可能还有多个正在进行的其他事务没有提交 , 因此在瞬时时刻 , 是有多个活跃的未提交事务id
  • 将这些未提交的事务id组成一个数组 , 数组里面最小的事务id记录为低水位 , 当前系统创建过的事务id的最大值+1记录为高水位
  • 这个数组array 和 高水位 , 就组成了“一致性视图” 。
有了一致性视图后 , 我们就可以判断一行数据的多版本可见性了 , 无论是“读已提交”还是“可重复读”级别 , 可见性判断规则是一样的 , 区别在于创建快照(一致性视图)的时间 。
在当前事务中 , 读取其他某一行的记录 , 对其中的版本号的可见性判断有五种情况(建议自己跟着捋一捋 , 挺重要的):