高性能MySQL学习笔记(5) —— MVCC

多版本控制

  Multi-Version Concurrency Control,多版本控制,每次操作,copy一份所要改的数据作为副本,副本之间通过一个版本号字段区分,并将副本的版本号+1,如果是更新操作,数据在副本上修改完后,要更新时候查看原纪录的版本号是否是副本版本号-1,是,更新,否(说明有其他修改事务在这期间修改了数据,使其版本号更新了),失败,重新取数据重新更新;如果是读操作,则是根据隔离级别读取小于等于当前事务版本号数据库数据(并不更改版本号)。
  上面的逻辑只是粗略说明MVCC是一个怎么样的东西,实际各个存储引擎的实现并不一定就这样,事实上,MVCC只是一个标准说明,而没有说明具体的实现。
  另外,有关事务隔离级别详见:事务隔离级别

理论

  高性能MySQL一书上说的MVCC实现方式:
  InnoDB的MVCC:通过给每条记录后面保存两个隐藏的字段来实现:一个是行的创建时间,一个是行的删除时间。当然,实际上存的不是时间值,而是系统版本号,每开始一个新的事务,系统版本号会自动递增。事务开始的版本号则是系统版本号,用来和查询到的每行记录的版本号作比较。下面是隔离级别为Repeatable read下MVCC具体操作:

SELECT
Innodb检查没行数据,确保他们符合两个标准:
  1、InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行。
  2、行的删除操作的版本一定是未定义的或者大于当前事务的版本号。确保了当前事务开始之前,行没有被删除(2)。
  符合了以上两点则返回查询结果。
INSERT
  InnoDB新插入的行以当前系统版本号为行版本号。
DELETE
  InnoDB为删除的每一行保存当前系统版本号作为删除标识。
UPDATE
  InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本。
  上面最重要的是系统版本号一个概念,系统版本号其实就是这个表从创建到此时所经历过的事务次数,开始为0。注意系统版本号不是行版本号,表的每个行的版本号一般是不一样的,因为不是每次操作都是针对整个表的。有了这个概念下面就好理解了:
  一个事务过程:

//注意,以下只针对隔离级别为Read commited和Repeatable read,其他隔离级别后面再说
0.事务开始,以下步骤数据操作并没有写到数据库中
1.系统版本号+1
2.事务版本号=系统版本号
3.进行操作
    (1)读操作:遍历数据库表,查找版本号<=事务版本号的行(这里包括了复制的行,等于是
因为可能这个事务对数据作了修改或增加),还有,这个时候可能有其他事务对数据做了修改,但
修改会改变行版本号(增加),选取<=,所以是看不见的(隔离级别控制),而若这个时候有其
他事务有增加新数据,同样版本号>当前系统版本号,所以不会出现幻读。
    (2)插入操作:增加一个新行,行版本号=系统版本号
    (3)删除操作:将该行的删除版本号(注意不是行版本号)=系统版本号,注意,这个时候并
没有物理删除,只是做了一个标记(如果最后不删除,或则哪怕删除,会存在类似undo.log文件
中,用来回滚)
    (4)更新操作:复制所更新行记录,将原行进行删除操作,新行版本号=系统版本号
4.提交事务(Commit),试图更新数据:
    (0)系统版本号+1
    (1)对比数据行的副本与原数据,若副本的行版本号=原数据行版本号,更新成功,否则失败,回滚操作。
    (2)若有行数据的删除版本=当前系统版本号,删除数据行。
5.操作成功,更新数据。

  再说下其他两个隔离级别:Read commited和Serializable,这两个级别下,MVCC是没用的,因为Read commited总是读取最新的数据,而不是符合当前事务版本的数据行,而Serializable呢,则会使对当前所有读取的行加锁,所以MVCC完全没用。

实现

  以上,其实说的还是理论上的东西,实际上的实现并不是完全如此,就比如第(4)点更新操作,是如何判断复制行是哪条行的复制?所以还需要一个字段,事实上,MySQL也的确如此做的,每一行其实有增加三个隐藏字段:
  1. 6字节的事务ID(DB_TRX_ID ),这个就是系统版本号(也是行版本号)
  2. 7字节的回滚指针(DB_ROLL_PTR),这个就是删除版本号,实现上是用来回滚,只有当undo.log缓存不足时才删除。
  3. 隐藏的ID,这就是每条行的id,用以区分原纪录与副本
  其中,7字节回滚指针有关InnoDB事务模型,我这里简单说两个比较重要的概念:
  redo.log:重做日志,就是每次mysql在执行写入数据前先把要写的信息保存在重写日志中,但出现断电,奔溃,重启等等导致数据不能正常写入期望数据时,服务器可以通过redo.log中的信息重新写入数据。
  undo log:撤销日志,与redo log恰恰相反,当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的状态。
  具体详情,这里我自己并没有深入研究,详情可以参考:Mysql中的MVCC(老码农的专栏)

MVCC与乐观锁

  搜索查了一下二者的关系,发现很多说法,众说纷纭,这里我个人觉得这两者的关系是:
  二者均是概念上的思想,并不是实现,而二者也不是等于或包含被包含关系,因为,乐观锁可以通过MVCC这种思路实现,也可以通过其他方法实现,比如CAS;但是MVCC本身只是想实现非阻塞的读,可以认为MVCC是行级锁的一个变种,很多情况避免了加锁操作,但不是没有加锁,写操作是有加锁的,只是加的是必要的行,事实上MVCC的实现有乐观并发控制,也有悲观并发控制的。

总结

  总的来说,MVCC就是用空间换时间,用复制一行来操作,而不是对行加锁,减少了加锁操作,之后查看原行是否在这期间有被更新过,若无操作成功,若有操作失败,回滚重新操作。适用于单行操作,如果过多多行更新操作的话,失败率很高,性能效率很低。

猜你喜欢

转载自blog.csdn.net/a327369238/article/details/52808865
今日推荐