mysql MVCC 多版本并发控制

前言

MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。大多数的MVCC都实现了非阻塞的读操作,写操作也只锁定必要的行。

MVCC 多版本并发控制 概念

Multiversion concurrency control 多版本并发控制:并发访问(读或者写)数据库时,对正在事务内处理的数据做多版本的管理,用来避免由于写操作的堵塞,而引发读操作失败的并发问题。
数据库默认隔离级别:RR(Repeatable Read,可重复读),MVCC主要适用于Mysql的RC,RR隔离级别。串行化由于对行加锁,也就不需要MVCC了。

实现

  MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据是一致的。根据事务开始的时间不同,每个事物对同一张表,同一时刻看到的数据可能是不一样的。
  背景:如果有多个写操作,多个读操作,可能两次读数据都不一样,又可能后面一次读取到写入的数据,为了解决读数据不一致的情况,有人提出了很多方法来解决这类问题,这类问题的解决方法叫做并发控制方法,MVCC也是其中一个方法。
  MVCC的思想就是:通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图,但这样也会造成会一行记录有多份
  主要实现就是我们看下,首先了解在MySQL中建表时,每个表都会有三列隐藏记录,其中和MVCC有关系的有两列:

  • 数据行的版本号 (DB_TRX_ID)
  • 删除版本号 (DB_ROLL_PT)

没一行数据都会有这里两列,记录版本号,我们来看下增删改查的对版本号的影响:

  • 插入、增加
    插入记录,数据行版本号为1,如下:
id test_id DB_TRX_ID DB_ROLL_PT
5 68 1 null
6 78 1 null
  • 修改、更新
    修改数据的时候 (1)会先复制一条当前记录行数据,并更新数据行版本号,删除版本号置为null。(2)最后把原来的数据行的删除版本号标记为当前新纪录的版本号。
begin;   --   获取全局系统事务ID 假设为 10
update test_zq set test_id = 22 where id = 5;
commit;

更新id =5 的记录:

id test_id DB_TRX_ID DB_ROLL_PT
5 68 1 10
6 78 1 null
5 22 10 null
  • 删除操作
    删除操作不是直接物理删除,而是将删除版本号置为最新的版本号
begin;--获得全局事务ID = 3
delete test_zq where id = 6;
commit;
id test_id DB_TRX_ID DB_ROLL_PT
5 68 1 null
6 78 1 3
  • 读取、查询
    读取数据是有条件的,要同时满足一下两个条件:
    (1)创建版本号:小于 or 等于当前事务版本号,这样保证数据是当前事务之前的数据或当前事务已经修改的数据。
    (2)删除版本号:未指定版本号 or 大于当前事务版本号,表明是没删除的数据

有点绕口,重点是注意到要有快照,我们会陷入这样的逻辑黑洞里:
(1)查询数据的事务版本号是2,有一个事务在update 数据,update 事务版本号是3,并且提交了事务。
(2)事务2开始读数据,update 操作会有两个记录,老记录满足查询条件,最后把老记录给查询出来了,也就是如下表的 id =5 的第一条记录。

id test_id DB_TRX_ID DB_ROLL_PT
5 68 1 3
6 78 1 null
5 22 3 null

update 改为delete 似乎也是这样的,但我自己理解,这里有一个误解:

  • 当开启事务的时候会生成一个快照,什么时候开启事务,从操作数据库的那一刻起,事务2读数据,一定是在事务3开始之前,这样快照里就是没有update 、 delete的数据
  • 如果其他事务已经提交了修改,那么此事你读数据库,你的事务版本号绝对高于已经提交的事务版本号

客观上,MVCC就是乐观锁的一整实现方式,就是每行都有版本号,保存时根据版本号决定是否成功。InnoDB中通过undo log实现了数据的多版本,而并发控制通过锁来实现。
有些理解可能是错的,希望大家看到后可以及时反馈,让我迷途知返。

主要解决问题

可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。大多数的MVCC都实现了非阻塞的读操作,写操作也只锁定必要的行。解决了在REPEATABLE READ和READ COMMITTED两个隔离级别下读同一行和写同一行的两个事务的并发。mvcc主要就是为了让读写不冲突。

  • Read Committed : 一个事务读取数据时总是读这个数据最近一次被commit的版本。
  • Repeatable Read : 一个事务读取数据时总是读取当前事务开始之前最后一次被commit的版本(所以底层实现时需要比较当前事务和数据被commit的版本号)。

举例子分析

举个简单的例子:

  • 一个事务A(txnId=100)修改了数据X,使得X=1,并且commit了
  • 另外一个事务B(txnId=101)开始尝试读取X,但是还X=1。但B没有提交。
  • 第三个事务C(txnId=102)修改了数据X,使得X=2。并且提交了事务B又一次读取了X。

根据数据库隔离级别,我们可以看到:

  • 这时如果事务B是Read Committed。那么就读取X的最新commit的版本,也就是X=2
  • 如果事务B是Repeatable Read。那么读取的就是当前事务(txnId=101)之前X的最新版本,也就是X被txnId=100提交的版本,即X=1。

参考博客

一文理解Mysql MVCC
【MySQL(5)| 五分钟搞清楚 MVCC 机制】

你可能感兴趣的:(数据库)