【Java架构学习交流】从基本概念深入到实现,跟面试官侃半小时MySQL事务隔离性( 二 )


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