制作网站要钱吗,毕业设计做网站做什么好,网站运营有哪些岗位,南阳网站怎么推广MVCC机制解析#xff1a;提升数据库并发性能的关键
MVCC#xff08;Multi-Version Concurrency Control#xff09; 多版本并发控制 。
MVCC只在事务隔离级别为读已提交(Read Committed)和可重复读(Repeated Read)下生效。
MVCC是做什么用的
MVCC是为了处理 可重复读 和…MVCC机制解析提升数据库并发性能的关键
MVCCMulti-Version Concurrency Control 多版本并发控制 。
MVCC只在事务隔离级别为读已提交(Read Committed)和可重复读(Repeated Read)下生效。
MVCC是做什么用的
MVCC是为了处理 可重复读 和 读已提交 事务隔离级别下在同一事务里多次执行同一SQL查询语句不会因为其他事务的横插一脚对数据进行修改后导致最终得到的结果不一致。如下图所示事务B的两次查询得到的结果不一样。 MVCC是怎么实现的
在 串行读 这一事务隔离级别里面为了保证较高的事务隔离性采用了将所有的操作加锁互斥将事务的执行变为顺序执行相当于单线程的方式以达到其高隔离性。
而mysql在 可重复读 和 已提交读 事务隔离级别下他的隔离性是借助MVCC机制来保证的MVCC机制呢也不是通过加锁互斥来保证隔离性的是通过 Undo日志版本链 和 Read View机制 实现的。避免了频繁的加锁互斥阻塞。
Undo日志版本链
Undo日志是什么
undo日志是回滚日志在mysql对某一数据进行修改更新时会将其更新前的数据保存的undo回滚日志里等当事务执行失败时用来进行数据回滚的。
而什么是undo日志版本链呢
就是一行数据被多个事务依次修改后这条数据就会有很多条undo日志这些undo日志就会通过一个 roll_pointer 字段进行串联起来会形成这条数据的历史记录版本链这个就是undo日志版本链。
roll_pointer 字段哪里来的呢
在mysql数据库里数据表都会有两个隐藏的字段属性 trx_id事务id 和 roll_pointer回滚指针 其中 trx_id 是用来记录操作数据的而 roll_pointer 则是用来记录指向上一次修改的日志地址。 注意begin/start transaction这个开始/启动事务命令并不是一个事务的起点 而是在执行到第一条更新update、删除delete、插入insert语句时事务才真正的启动才会有真正的事务id。 后生成的trx_id要比先生成的trx_id大。 只有InnoDB数据引擎才支持事务。 下图中每一条数据都是一个undo回滚日志通过roll_pointer串联起来后就是undo日志版本链了。 如果第二次修改操作将name改为了 王五 又因为某些原因需要进行数据回滚就要拿到roll_pointer里记录的上一次也就是第一次修改操作的undo回滚日志地址将name回滚为 赵六 。 Read View机制
在可重复读隔离级别下一个事务在开始时执行任何的查询SQL脚本都会生成一个属于当前事务自己的 Read View一致性视图 这个视图在这个事务结束之间都是保持不变的除非在本事务里面自己执行了更新操作。 如果事务隔离级别是读已提交则 Read View一致性视图 是在每次执行查询SQL脚本时都会重新生成与可重复读隔离级别的在同一事务里保持不变不同。 这个视图是什么呢
这个视图是由执行查询SQL脚本这一时刻所有还未提交的事务id构建而成的数组和此时存在最大的一个事务id共同构建而成。
示意图一 示意图二 结合上面两图分析可知
事务A 第一次执行查询语句时刻所生成的ReadView的构成为 {[1002,1003,1004], 1004} 其中[1002,1003,1004]为未提交的事务idmin_id1002min_id最小事务id是从未提交事务id里获取的1004为当前最大的事务idmax_id1004。
事务A 第二次、三次执行查询语句时刻其ReadView的依旧为 {[1002,1003,1004], 1004} 。
事务B 第一次执行查询语句时刻所生成的ReadView的构成为 {[1002,1004], 1004} 其中[1002,1004]为未提交的事务idmin_id10021004为当前最大的事务idmax_id1004。
事务B 第二次执行查询语句时刻所生成的ReadView的依旧为 {[1002,1004], 1004} 。
根据事务A和事务B的ReadView可以得出一个相同的工具图用来判断某事务的某一次数据更新是否对select是可见的 事务里的select语句查询结果都是需要从undo日志版本链最新数据开始逐条与本事务的ReadView进行比对判断应该获取到哪一日志版本的数据为select语句的查询结果。 示意图三 比对规则 如果比较的undo日志的 trx_id小于min_id 则表示这个版本事务是已经提交的代表本次select这一事务可以查到这个数据。 如果比较的undo日志的 trx_id大于max_id 则表示这个版本事务是在本次select这一事务后面新启动的这种数据肯定是不可被查询到的。 如果比较的undo日志的 trx_id大于等于min_id 且 trx_id小于等于max_id 则再判断 trx_id是否是在未提交事务id数组里。 在未提交事务id数组里则表示这个版本数据是由未提交的事务所生成的这种数据不可被查询到除非这个未提交的事务就是自己。不在未提交事务id数组里则表示这个版本数据是由已经提交的事务所生成的这种数据可以被查询到。 案例一
那么事务A第一条select语句查询脚本执行时获取到的name是什么呢
先说结果查询到的name为赵六。
事务A执行第一条select语句时他的SQL执行顺序和SQL查询脚本执行时拿到的日志版本链及判断示意图如下。 上面在示意图二里也说了事务A的第一次执行查询语句时刻时其ReadView为 {[1002,1003,1004], 1004} 。 比较步骤如下
第一次 事务A从undo日志版本链拿到最后一次更新记录第二次修改记录得到trx_id为1003然后对照着上面的 判断工具图 进行比较大于min_id小于max_id所以1003事务id属于是 第二部分 且1003这一事务id处于还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1003,1004]所以这个事务进行的更新数据是不可被查询到的。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第二次 事务A从undo日志版本链拿到第二条日志记录第一次插入记录得到trx_id为1001然后同样对照着上面的 判断工具图 进行比较发现小于min_id的1002所以1001事务id属于是 第一部分 是已经提交了事务的他更新的数据就属于可以被查询到于是此时查询到的结果name为张三。
案例二
那么事务A第二条select语句查询脚本执行时获取到的name是什么呢
先说结果查询到的name依旧为赵六。
事务A执行第二条select语句时他的SQL执行顺序和SQL查询脚本执行时拿到的日志版本链及判断示意图如下。 上面在示意图二里也说了事务A的第二次和第三次执行查询语句时刻时其ReadView依旧为 {[1002,1003,1004], 1004} 。 比较步骤如下
第一次 事务A从undo日志版本链拿到最后一次更新记录第四次修改记录得到trx_id为1003然后对照着上面的 判断工具图 进行比较大于min_id小于max_id所以1003事务id属于是 第二部分 继续判断得知1003这一事务id处于还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1003,1004]即便此时事务1003已经提交了但是只要在事务A第一次执行查询语句时这个事务没有提交那他在事务A里就一直被标记的是未提交否则就会出现 不可重复读 问题所以这个事务进行的更新数据是不可被查询到的。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第二次 事务A从undo日志版本链拿到第二条日志记录第三次修改记录得到trx_id为1002然后同样对照着上面的 判断工具图 进行比较发现等于min_id的1002所以1002事务id属于是 第二部分 继续判断得知1002这一事务id处于还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1003,1004]所以这个事务进行的更新数据是不可被查询到的。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第三次 事务A从undo日志版本链拿到最后一次更新记录第二次修改记录得到trx_id为1003然后对照着上面的 判断工具图 进行比较大于min_id小于max_id所以1003事务id属于是 第二部分 且1003这一事务id处于还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1003,1004]所以这个事务进行的更新数据是不可被查询到的。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第四次 事务A从undo日志版本链拿到第二条日志记录第一次插入记录得到trx_id为1001然后同样对照着上面的 判断工具图 进行比较发现小于min_id的1002所以1001事务id属于是 第一部分 是已经提交了事务的他更新的数据就属于可以被查询到于是此时查询到的结果name为张三。
案例三
那么事务B第一条select语句查询脚本执行时获取到的name是什么呢
先说结果查询到的name为李四。
为什么事务B的一条查询语句和事务A的第二条查询语句是同时执行的但是结果不一样呢可以详细看看下面对事务的比较步骤。
事务B 第一次执行查询语句时刻其所生成的ReadView的构成为 {[1002,1004], 1004} 其中[1002,1004]为未提交的事务idmin_id10021004为当前最大的事务idmax_id1004。
事务B执行第一条select语句时他的SQL执行顺序和SQL查询脚本执行时拿到的日志版本链及判断示意图同案例二里的一样。
比较步骤如下
第一次 事务B从undo日志版本链拿到最后一次更新记录第四次修改记录得到trx_id为1003然后对照着上面的 案例二的判断工具图 进行比较大于min_id小于max_id所以1003事务id属于是 第二部分 继续判断得知1003这一事务id 不处于 还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1004]所以表示这个事务已经提交了这个事务所更新的数据版本可以被查询到查询结果就是name为李四。
案例四
事务A第三条select语句查询脚本执行时获取到的name是什么呢
先说结果查询到的name还是赵六。
事务A执行第三条select语句时他的SQL执行顺序和SQL查询脚本执行时拿到的日志版本链及判断示意图如下。 上面在示意图二里也说了事务A的第二次和第三次执行查询语句时刻时其ReadView依旧为 {[1002,1003,1004], 1004} 。 比较步骤如下
第一次 事务A从undo日志版本链拿到最后一次更新记录第五次修改记录得到trx_id为1004然后对照着上面的 判断工具图 进行比较大于min_id小于 等于 max_id所以1004事务id属于是 第二部分 且1004这一事务id 不处于 还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1003,1004]所以这个事务进行的更新数据是不可被查询到的。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第二次 比较过程和示例二里的第一次比较操作过程一样。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第三次 比较过程和示例二里的第二次比较操作过程一样。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第四次 比较过程和示例二里的第三次比较操作过程一样。
于是根据roll_pinter回滚指针上一版本数据的地址找到上一个版本数据。
第五次 比较过程和示例二里的第四次比较操作过程一样。得到查询结果name为张三。
案例五
那么事务B第二条select语句查询脚本执行时获取到的name是什么呢
先说结果查询到的name为李四。
事务B 第二次执行查询语句时刻其所生成的ReadView的依旧为 {[1002,1004], 1004} 。和其第一次执行查询语句时刻一样。
事务B执行第三条select语句时他的SQL执行顺序图和SQL查询脚本执行时拿到的日志版本链和上面案例三的一样。
虽然ReadView和案例三的不同为 {[1002,1004], 1004} 但是其形成的判断图还是一样的。 比较步骤如下
第一次 事务B从undo日志版本链拿到最后一次更新记录第五次修改记录得到trx_id为1004然后对照着上面的 判断工具图 进行比较大于min_id小于 等于 max_id所以1004事务id属于是 第二部分 且1004这一事务id 不处于 还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1004]即便此时事务1004已经提交了但是只要在事务B第一次执行查询语句时这个事务没有提交那他在事务B里就一直被标记的是未提交否则就会出现 不可重复读 问题所以这个事务进行的更新数据是不可被查询到的。和上面案例二的第一次比较一样
第二次事务B从undo日志版本链拿到第二条日志记录第四次修改记录得到trx_id为1003然后对照着上面的 案例二的判断工具图 进行比较大于min_id小于max_id所以1003事务id属于是 第二部分 继续判断得知1003这一事务id 不处于 还未提交的事务id构建而成的数组里未提交的事务id数组[1002,1004]所以表示这个事务已经提交了这个事务所更新的数据版本可以被查询到查询结果就是name为李四。
读已提交隔离级别又是怎么比较的
读已提交 已提交读事务隔离级别的undo日志版本链和 可重复读 是一样的Read View的生成规则就不同了 读已提交的 Read View一致性视图 是在每次执行查询SQL脚本时都会重新生成 与可重复读隔离级别的在同一事务里保持不变的定义不同。
这也就导致每次执行查询脚本时都会重新构建 还未提交的事务id数组 所以只要其他事务在本事务执行这一条查询脚本之前更新数据并提交了那他的更新数据就可以被本事务的这一次查询操作查询到。
其他的比对规则还是和 可重复读 事务隔离级别一样仅仅是ReadView在每次查询时需要重新生成。
可以理解为 读已提交 事务隔离只会读到最后一次提交了更新操作事务的数据。