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


事务A启动 , 拍个快照
此时未提交的事务id有:7 , 8 , 9
一致性视图:数组array[7,8,9]+高水位16(15+1)
对于任意一行数据的可见性判断如下:
1)小于7的 , 可见
2)大于16的 , 说明是快照后产生的 , 不可见
3)10-15 , 不在数组array中 , 说明已经提交了 , 可见
4)7 , 8 , 9在array中 , 说明未提交 , 不可见
两个重要结论:
InnoDB利用了“所有数据都有多个版本”的这个特性 , 实现了“秒级创建快照”的能力 。 MVCC的实现 , 就是根据当前事务的事务id为依据创建“一致性视图” , 利用一致性视图来判断数据版本的可见性 。 3.隔离性实战下面 , 我们来两个实战案例 , 将上面的基础概念与实现融会贯通吧 。
1)并发select&update案例
id=1的value初始为1 。
「Java架构学习交流」从基本概念深入到实现,跟面试官侃半小时MySQL事务隔离性
文章图片
我们看下 , 在不同隔离级别 , Time5、Time7、Time9事务A查询到的value分布为多少 。
“读未提交”:2 , 2 , 2“读以提交”:1 , 2 , 2“可重复读”:1 , 1 , 2串行化:1 , 1 , 2(注意 , 这里在事务A提交前 , 事务B都会阻塞 , 直到事务A提交后才能执行)2)并发update案例
id=1的value初始为1 , 在可重复读级别:
「Java架构学习交流」从基本概念深入到实现,跟面试官侃半小时MySQL事务隔离性
文章图片
我们看一下 , 你猜猜事务A和事务B读取的value是多少?
答案是:1和3
可能会产生困惑 , 事务A在启动后快照 , 所以读到了1是正常的 , 但是事务2在启动的时候快照了 , 然后在自己的事务中+1 , 怎么会读到3而不是2呢?
原因很简单 , 即使是在可重复读的级别 , 事务更新数据的时候 , 只能用当前读(想想也能理解 , 不然update就出现数据不一致了) 。
如果当前的记录的行锁被其他事务占用的话 , 就需要进入锁等待 。 而读提交的逻辑和可重复读的逻辑类似 , 它们最主要的区别是:在可重复读隔离级别下 , 只需要在事务开始的时候创建一致性视图 , 之后事务里的其他查询都共用这个一致性视图;在读提交隔离级别下 , 每一个语句执行前都会重新算出一个新的视图 。
这里 , 我们需要注意的是事务的启动时机 。
begin/starttransaction命令并不是一个事务的起点 , 在执行到它们之后的第一个操作InnoDB表的语句 , 事务才真正启动,一致性视图是在执行第一个快照读语句时创建的 。 如果你想要马上启动一个事务 , 可以使用starttransactionwithconsistentsnapshot这个命令 , 一致性视图是在执行starttransactionwithconsistentsnapshot时创建的 。 4.关于幻读首先明确一下 , 什么是幻读?开篇介绍了什么是幻读 , 这里再申明一下幻读出现的场景
第一:事务的隔离级别为可重复读 , 且是当前读第二:幻读仅专指新插入的行 , 在范围查询中 , 后一次查询出现了新的数据行 。前文已经提到了 , 对于普通数据库 , 需要到可串行化的隔离级别才能解决幻读问题 。
而对于InnoDB存储引擎来说 , 在可重复读级别下就能解决幻读问题 。
InnoDB存储引擎有三种行锁算法:
行锁:当个行记录上的锁间隙锁:GapLock , 锁定一个范围 , 但不包含记录本身Next-KeyLock:就是行锁+间隙锁 , 同时锁上一个范围 , 并且锁定记录本身