MVCC 的作用是什么?

避免因为写锁的阻塞而造成读数据的并发阻塞问题。可以这么理解,在引擎层做了一个逻辑上(注意是逻辑上,不是物理上)的读写分离。

它是通过保存数据的多个历史版本,根据版本号来决定数据是否对事务可见。在InnoDB内部中,会记录一个全局的活跃读写事务ID数组,其主要根据事务ID 大小用来判断事务的可见性。

所以它可以使数据不用加锁就达到事务隔离的效果。

序列

  1. 事务日志
  2. 版本+模型
  3. 一致性视图

几种事务日志?

binlog。Mysql服务层产生的日志,用来做数据复制与同步以及故障恢复的。

redo log。Mysql数据日志记录,Mysql的写入流程是先写日志,再写磁盘,这里的日志就是redo log。这就是Mysql的WAL技术(Write-Ahead Logging)(Redis的写入也是这种方式)。具体一点就是,当一条数据需要更新的时候,InnoDB引擎会先把数据写入redo log,然后更新内存(change buffer),到这里就算成功了。数据会在适当的时候(什么是适当的时候?后面单独讲)去刷磁盘。

undo log。跟 redo log 是相反的,回放日志记录。它的生成方式是这样的:

  • insert => delete

    insert into t(a) values(1);   => id = 1
    ---
    delete t where id = 1;
    复制代码
  • delete => insert

    delete t where id = 1;       => id => 1, a => 1
    ---
    insert into t(id, a) values(1, 1);
    复制代码
  • update => update

    update t set a = 2 where id = 1;    => id => 1, a => 1
    ---
    update t set a = 1 where id = 1;
    复制代码

版本模型

事务版本号

在InnoDB下,每个事务都有一个唯一的事务ID(transaction id),它是在事务开始的时候向事务系统申请(全局ID生成器)的。

数据版本

每一次的插入及更新操作都会视为一个版本。这个版本的架构如下:

mvcc

  • trx_id。事务ID
  • roll ptr。指向上一个数据版本的指针,用于回退版本

根据这两个字段可以形成一个单链表,使得数据都可以溯源;在查询的时候,就可以有选择性的展示哪个版本的数据。那这个有选择性的展示是怎么实现的呢?

一致性视图

InnoDB为每个事务维护了一个数组,这个数组用来保存这个事务启动的瞬间,当前活跃的事务ID。这个数组里有两个水位值:

  • 低水位:事务ID 最小值
  • 高水位:事务ID 最大值 + 1

这两个水位值就构成了当前事务的一致性视图(Read-View)

当前事务(S1)在启动的瞬间,会生成一个活跃事务ID 数组。某个数据版本的trx_id。会有以下几种情况:

【【1,2,5】,【4,6】,【7,8】】

  • (trx_id == 2 && trx_id < low woter)落在绿色区域(【1,2,5】)。表示这个事务是已提交的或者就是自己。那这个数据版本对S1是可见的
  • 落在黄色区域(【4,6】)。
    • (trx_id == 4 && trx_id >= low woter && trx_id < hign woter)如果trx_id in active array。说明这个数据版本的事务还没提交。对S1不可见
    • (trx_id == 5 && trx_id not in 【low woter, hign woter】)如果trx_id not in active array。对S1可见。为什么会有这种情况呢?
      • 黄绿蓝区域是由水位来隔开的
      • 因为低水位往后的且小于高水位的都会在黄色区域
      • 当前事务是6,事务4未提交,事务5已经提交了
  • (trx_id == 8 && trx_id >= hign woter)落在红色区域(7,8)。在S1启动的时候,我这个版本还没生成呢。对S1不可见

下面来举个例子看下

create table t(name varchar, age int, sex int, address varchar) engine=InnoDB;

insert into t(name, age, sex, address) values('tom', 23, 2, 'beijing');
复制代码
trx_id = 1 trx_id = 2 trx_id = 3 trx_id = 4 trx_id = 5
begin; update t set age = 24, address = ‘nanjing’; commit;        
  begin;      
    begin; active=>[2,3]    
      begin;active=>[2,3,4]  
        begin;active=>[2,3,4,5]
        update t set age = 5; commit;
    update t set age = 6;commit    
      update t set age = 7;commit  
  select age from t;      

我们来画一下以上事务的版本结构

一致性视图

捋一下 trx_id = 1 的事务是怎么读到数据的?

  1. 找到 age = 3 版本的 trx_id = 3。判断 >= 当前事务的高水位,不可见。继续根据 ptr 找 age = 4 的版本。
  2. 找到 age = 4 版本的 trx_id = 4。判断 >= 当前事务的高水位,不可见。继续根据 ptr 找 age = 5 的版本。
  3. 找到 age = 5 版本的 trx_id = 5。判断 >= 当前事务的高水位,不可见。继续根据 ptr 找 age = 24 的版本。
  4. 找到 age = 24 版本的 trx_id = 1。判断 < 当前事务的低水位,可见。

这么一套下来,trx_id = 1 的事务 不管在什么时候查询,看到的数据都是 age = 24。这就是一致性视图。