熟悉数据库隔离级别的人都知道,在RR(可重复读)隔离级别下,不管什么时候屡次执行相同的SELECT快照读语句,获得的结果集都是彻底同样的,即使两次SELECT语句执行期间,其余事务已经改变了该查询结果并已经提交。数据库
对于这一机制的实现原理,网上常见的一种解释以下:优化
####MVCC在MySQL的InnoDB中的实现 在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据什么时候被建立,另一个记录这行数据什么时候过时(或者被删除)。 在实际操做中,存储的并非时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在可重读Repeatable reads事务隔离级别下: SELECT时,读取建立版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。 INSERT时,保存当前事务版本号为行的建立版本号 DELETE时,保存当前事务版本号为行的删除版本号 UPDATE时,插入一条新纪录,保存当前事务版本号为行建立版本号,同时保存当前事务版本号到原来删除的行
上述解释确实可让读者简单快速地理解MVCC机制的核心思想,我最开始也觉得本身已经彻底理解MVCC机制的实现原理了,可是当我试图利用上述原理去解释某些特别的实测结果时,却发现老是难以自圆其说。spa
如上图所示,事务2中,虽然记录A的建立版本号1小于当前事务版本号2,可是依然没法读取到记录A。指针
如上图所示,事务2中,虽然记录A的建立版本号小于当前事务版本号2,且记录A已经提交,可是第二次查询时,事务2依然没法查询到任何记录。 code
如上图所示,事务1中,虽然记录A的建立版本号大于当前事务版本号1,可是事务1依然能够查询到记录A。对象
可见,常见的对MVCC版本的实现原理的理解彷佛遗漏了某些关键逻辑,致使没法解释不少特殊状况。blog
下面咱们来一块儿看一下InnoDB中,MVCC机制究竟是如何控制记录的可见性的。事务
InnoDB RR隔离界别下,MVCC对记录可见性控制,还有以下关键断定逻辑:it
1. 事务ID并不是在事务begin时就分配,而是在事务首次执行非快照读操做(SELECT ... FOR UPDATE/IN SHARE MODE、UPDATE、DELETE)时分配。table
注: 若是事务中只有快照读,InnoDB对只有快照读事务有特殊优化,这类事务不会拥有事务ID,由于它们不会在系统中留下任何修改(甚至连锁都不会建),因此也没有留下事务ID的机会。 虽然使用SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID(); 查询此类事务ID时,会输出一个很大的事务ID(好比328855902652352),不过这只是MySQL在输出时临时随机分配的一个用于显示的ID而已。
2. 每一个事务首次执行快照读操做时,会建立一个read_view对象(能够理解为在当前事务中,为数据表创建了一个逻辑快照,read_view对象就是用来控制此逻辑快照的可见范围的)。事务提交后,其建立的read_view对象将被销毁。
read_view对象中有三个关键字段用于判断记录的可见范围。它们分别是trx_ids、low_limit_id、up_limit_id。 1. read_view->trx_ids:建立该read_view时,记录正活跃的其余事务的ID集合。事务ID在集合中降序排列,便于二分查找。 2. read_view->low_limit_id:当前活跃事务中的最大事务ID+1(即系统中最近一个还没有分配出去的事务号)。 3. read_view->up_limit_id:当前活跃事务中的最小事务ID。
3. 若是记录的版本号比本身事务的read_view->up_limit_id小,则该记录的当前版本必定可见。由于这些版本的内容造成于快照建立以前,且它们的事务也确定已经commit了。或者若是记录的版本号等于本身事务的事务ID,则该记录的当前版本也必定可见,由于该记录版本就是本事务产生的。
4. 若是记录的版本号与本身事务的read_view->low_limit_id同样或比它更大,则该版本的记录的当前版本必定不可见。由于这些版本的内容造成于快照建立以后。
不可见有以下两层含义: 1. 若是该记录是新增或修改后造成的新版本记录,则对新增和修改行为不可见,即看不到最新的内容; 2. 若是该记录是标记为已删除造成的新版本记录,则对该删除行为不可见,便可以看到删除前的内容。
5. 当没法经过4和5快速判断出记录的可见性时,则查找该记录的版本号是否在本身事务的read_view->trx_ids列表中,若是在则该记录的当前版本不可见,不然该记录的当前版本可见。
6. 当一条记录判断出其当前版本不可见时,经过记录的DB_ROLL_PTR(undo段指针),尝试去当前记录的undo段中提取记录的上一个版本进行4~6中一样的可见性判断,若是能够则该记录的上一个版本可见。
4、用例的正确解释
为了更好的检验上面新增的知识,对部分用例进行了适当的扩展。
要正确理解InnoDB RR隔离级别下MVCC的可见性控制逻辑,需注意补充以下关键知识:
1. 事务ID并不是事务begin时分配,是延迟到须要分配时才分配的。
2. 事务在首次快照读时建立快照,并将快照版本的可见范围控制信息记录在read_view对象中。