如果trx_id < m_up_limit_id,那么表明“最新修改该行的事务”在“当前事务”创建快照之前就提交了,所以该记录行的值对当前事务是可见的。跳到步骤5。
如果trx_id >= m_low_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤4。
如果m_up_limit_id <= trx_id < m_low_limit_id, 表明“最新修改该行的事务”在“当前事务”创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表trx_ids进行查找(源码中是用的二分查找,因为是有序的)
(1) 如果在活跃事务列表m_ids中能找到id为trx_id的事务,表明①在“当前事务”创建快照前,“该记录行的值”被“id为trx_id的事务”修改了,但没有提交;或者②在“当前事务”创建快照后,“该记录行的值”被“id为trx_id的事务”修改了(不管有无提交);这些情况下,这个记录行的值对当前事务都是不可见的,跳到步骤4;
(2) 在活跃事务列表中找不到,则表明“id为trx_id的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见,跳到步骤5。
在该记录行的DB_ROLL_PTR指针所指向的undo log回滚段中,取出最新的的旧事务号DB_TRX_ID, 将它赋给trx_id,然后跳到步骤1重新开始判断。
将该可见行的值返回。
read committed与repeatable read的区别有了上面的知识铺垫后,就可以从本质上区别read committed与repeatable read这两种事务隔离级别了。
With REPEATABLE READ isolation level, the snapshot is based on the time when the first read operation is performed. With READ COMMITTED isolation level, the snapshot is reset to the time of each consistent read operation.
在InnoDB中的repeatable read级别, 事务begin之后,执行第一条select(读操作)时, 会创建一个快照(read view),将当前系统中活跃的其他事务记录起来;并且在此事务中之后的其他select操作都是使用的这个read view对象,不会重新创建,直到事务结束。
在InnoDB中的read committed级别, 事务begin之后,执行每条select(读操作)语句时,快照会被重置,即会基于当前select重新创建一个快照(read view),所以显然该事务隔离级别下会读到其他事务已经提交的修改数据。
那么,现在能解释上面演示幻读问题时,出现的诡异结果吗?我的理解是,因为是在repeatable read隔离级别下,肯定还是快照读,即第一次select后创建的read view对象还是不变的,但是在当前事务中update一条记录时,会把当前事务ID设置到更新后的记录的隐藏字段DB_TRX_ID上,即id == m_creator_trx_id显然成立,于是该条记录就可见了,再次执行select操作时就多出这条记录了。
if (id < m_up_limit_id || id == m_creator_trx_id) { return(true); }另外,有了这样的基本认知后,如果你在MySQL事务隔离相关问题遇到一些其他看似很神奇的现象,也可以试试能不能解释得通。
总结通过学习MySQL事务隔离级别及MVCC原理机制,有助于加深对MySQL的理解与掌握,更为重要的是,如果让你编写一个并发读写的存储程序,MVCC的设计与实现或许能给你一些启发。