根据《高性能mysql》的书籍,mvcc的实现应该大致如下。
1、每行数据后面有两个隐藏字段。 一个字段为更新标识,一个字段为删除标识,记录内容为更新/删除时的系统版本号。
2、在选择过程中,根据当前事务版本号过滤以下数据:
3、update操作实际上不是增加一行记录,修改为原始数据。
4、delete也不是物理删除,而是在删除识别字段中附加版本号。
看起来很好,但innodb的mvcc实现却不是这样!
在innodb中,两个隐藏字段分别为DATA_TRX_ID和DATA_ROLL_PTR。 如果没有主键,还会添加一个隐藏的主键列。
DATA_TRX_ID
用6个字节记录最近更新此行记录的事务ID
DATA_ROLL_PTR
表示指向该行中“回退段”(rollback segment )的指针。 大小为7个字节,InnoDB从该指针中查找以前版本的数据。 该行记录中的所有旧版本都在还原中以链表的形式组织。 在网上搜索后发现,大致以以下形式记录了每个版本修改后的数据,不是操作的逆操作。
因此,每次实际执行update操作时,都不会创建新副本。 而是将的数据行copy放入还原日志中,然后更改数据行,从而更改DATA_TRX_ID和DATA_ROLL_ID。
既然我们已经讨论了支持mvcc功能的列结构,我们来看看mysql的四个事务隔离级别与mvcc之间的关系。
1 .脏读
无论mvcc如何,每次选择最新的数据时,如果另一个事务的数据已被物理写入或修改,它都会去读取并忽略下一个DATA_TRX_ID。
2 .提交阅读
在mvcc的使用中,innodb引入了ReadView这一方式。 生成ReadView时,所有当前活动事务(所有未提交的事务)的版本号都将收集到一个队列中的m_ids中,当前系统版本号1也将添加到队列中。 可以找到版本号的最小值up_limit_id和最大值low_limit_id。 在选择过程中,对于单个数据,与m_ids进行比较将产生以下结果:
如果小于up_limit_id,则表示事务是有效数据,因为在ReadView打开之前已提交该事务。
如果大于low_limit_id,则表示该事件是在ReadView打开后提交的,并且是无效数据。 在这种情况下,它将沿着还原日志链表向上移动,直到找到有效的内容
对于up_limit_id
当在PS.undolog中进行搜索时,只有data_trx_id小于up_limit_id才有效,并且可以满足条件c。