1、MySQL可重复读级别下,由于MVCC引发的BUG,下图1为相应的Java代码,其中事务1的生命周期最长,循环开启的事务二、三、4。。。与事务1存在并发问题html
图1并发
解决方案:将方法userRemoteService.addUser和UserBaseContext.getUserBaseByUserId放在两个方法中,避免事务的并发问题post
2、MVCC简介:Multiversion Concurrency Control,多版本并发控制机制,行级锁的一个变种, 可是它在不少状况下避免了加锁操做, 所以开销更低,实现了非阻塞的读操做,只在 READ COMMITTED
和 REPEATABLE READ
两个隔离级别下工做,由于 READ UNCOMMITTED
老是读取最新的数据行,而SERIALIZABLE
则会对全部读取的行都加锁性能
3、数据行隐藏字段设计
6字节的DATA_TRX_ID 标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+13d
7字节的DATA_ROLL_PTR 指向当前记录项的rollback segment的undo log记录,找以前版本的数据就是经过这个指针指针
6字节的DB_ROW_ID,当由innodb自动产生汇集索引时,汇集索引包括这个DB_ROW_ID的值,不然汇集索引中不包括这个值.,这个用于索引当中code
DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候htm
对于有有三个字段id、name、balance的表,其中id为主键,实际的拥有的列以下blog
图2
4、具体的执行过程:
SELECT
Innodb检查每行数据,确保他们符合两个标准:
一、InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务以前已经存在的,或者是由当前事务建立或修改的行
二、行的删除操做的版本必定是未定义的或者大于当前事务的版本号,肯定了当前事务开始以前,行没有被删除
符合了以上两点则返回查询结果。
INSERT
InnoDB为每一个新增行记录当前系统版本号做为建立ID,该操做没有回滚指针,由于不存在历史版本
DELETE
InnoDB为每一个删除行的记录当前系统版本号做为行的删除ID。
UPDATE
InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号做为了删除行的版本
事务执行过程当中,只有在第一次真正修改记录时(好比使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的,下面以update为例说明
begin->用排他锁锁定该行->记录数据行数据快照到undo log,将修改前的行标记为删除,写事务编号->新增行保存修改后的值,写事务编号,回滚指针指向undo log中的修改前的行->将undo log写到磁盘->数据写磁盘->commit
图3
优势:
保存这两个额外系统版本号,使大多数读操做均可以不用加锁。这样设计使得读数据操做很简单,性能很好。
缺点:
每行纪录都须要额外的存储空间,须要作更多的行检查工做,以及一些额外的维护工做。
5、read view
1.判断当前版本数据项是否可见
2.提交读的隔离级别下,事务开始后到结束前,每次读取数据都会生成一个read view,而可重复读的隔离级别,只有事务开始后第一次读取数据,才生成read view
2.在innodb中, 每建立一个新事务, 存储引擎都会将当前系统中的活跃事务列表建立一个副本(read view
), 副本中保存的是系统中当前不该该被本事务看到的其余事务id列表
3.当用户在事务中要读取某行记录的时候, innodb会将该行当前的版本号与该read view进行比较
4.比较流程:read view中最先的事务id为tmin,最迟的事务id为tmax,当前事务id为t0
图4
参考文章
https://www.cnblogs.com/williamjie/p/9492810.htmlhttps://www.jianshu.com/p/db334404d909https://juejin.im/post/5c9b1b7df265da60e21c0b57