0%

MySQL实战笔记--深入理解RR隔离级别

一致性视图的创建时间

  • start transaction 并不是一个事务的起点,在执行到它之后的第一个操作InnoDB表的语句,事务才真正启动。如果要立即启动一个事务,可以使用start transaction with consistent shapshot
  • 在使用start transaction 时,一致性视图是在执行第一个快照读语句是创建的;
  • 使用start transaction with consistent shapshot 时,一致性视图是在该语句执行时就创建了;

两种视图

  • 普通视图:一个用查询语句定义的虚拟表,在调用的时候执行查询语句生成结果;
  • 一致性读视图:InnoDB的MVCC是使用一致性读视图实现的,该视图是RC和RR隔离级别实现的基础;

事务快照的实现

  • InnoDB中每个事务都有一个唯一的transaction id,是按照顺序严格递增的;

  • 每行数据会有多个版本,每次有事务更新该行数据时,就会生成一个新的数据版本,并将该事务ID记录到该版本的row trx_id中,表示该版本是由哪个事务产生的;同时旧版本也会保留,并且可以通过新版本访问旧版本数据;(链表?)

  • 行数据版本示例:
    数据版本
    说明:

    1. 图中在物理上只存在V4,也就是最新版本,V1、V2和V3都是逻辑上存在的,是通过Undo日志计算出来的;
    2. 也就是说物理上,每行数据只会保留其最新版本,及所有未提交事务产生的Undo日志;
  • 可重复读:事务启动时,能够看到所有已提交的事务的结果,能够看到事务内的结果,但在该事务执行期间,其他事务的更新对其不可见;

  • 事务启动时,InnoDB生成一个数组,保存事务启动时所有处于active状态的事务的ID;数组中的最小事务ID为低水位,当前系统中已经创建过的最大事务ID加1为高水位;

  • 该视图数组以及高水位,一起构成了一个该事务的一致性读视图;
    一致性视图

  • 对于当前事务来说,数据版本的可见性规则如下:

    1. 如果数据版本中的row trx_id小于低水位,表示该数据已提交,则该数据版本对当前事务来说是可见的;
    2. 如果数据版本中的row trx_id等于当前事务ID,表示数据版本是当前事务产生的,则该数据版本对当前事务来说也是可见的;
    3. 如果数据版本中的row trx_id大于等于高水位,表示数据版本是未来某个事务产生的,则该数据版本对当前事务来说不可见;
    4. 如果数据版本中的row tx_id在低水位和高水位之间,此时如果row trx_id在当前事务的视图数组中,表示该数据版本是由未提交的事务产生的,则该数据版本对当前事务来说不可见;如果row trx_id不在当前事务的视图数组中,表示该数据版本是由已提交的事务产生的,则该数据版本对当前事务来说是可见的;
  • 基于以上规则,实现了事务的快照;

  • InnoDB 利用了”所有数据都有多个版本”的这个特性,实现了秒级快照;

示例

  • 表结构如下:

    1
    2
    3
    4
    5
    6
    mysql> CREATE TABLE `t` (
    `id` int(11) NOT NULL,
    `k` int(11) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB;
    insert into t(id, k) values(1,1),(2,2);
  • 事务信息如下:
    事务提交顺序

  • 问题:

    • 事务A的查询结果?
    • 事务B的查询结果?
  • 分析

    • 假设id=1的数据在事务ABC开始前,row trx_id = 90;
    • 假设事务ABC的transaction id分别为100,101,102;
    • 假设事务A开始前还有active 的事务,其id = 99,且再没有其他事务;
    • 则按照之前的一致性视图描述,事务A的一致性视图数组为[99, 100],其中低水位为99,高水位为101;
    • 同理可得事务B的一致性视图数组为[99,100,101],其中低水位为99,高水位为102;
    • 同理可得事务C的一致性视图数组为[99,100,101,102],其中低水位为99,高水位为103;
    • id=1的数据的版本列表为:最新版本的row trx_id = 101,次新版本的row trx_id = 102,之后是row trx_id = 90;
    • 则按照RR隔离级别下可见性规则,可知,事务A在查询时,首先发现最新版本101等于其视图数组的高水位101,则该版本不可见,继续找旧版本;接着发现102大于其高水位101,也不可见,最后发现90小于其低水位99,可见,得到最后查询结果是1;
    • 事务B在查询开始时,按照可见性规则,可知,事务B发现最新版本101等于当前事务ID 101,则是该事务内更新,可见,得到最后查询结果是3;

其他点

  • 数据更新时是先读后写的,这里的读,是“当前读”,即读当前时刻数据版本的最新值;
  • 当有多个事务同时读数据最新值时,需要加锁来保证顺序;比如:如果示例中的事务C没有立即提交,而是在事务B的更新和提交之间再提交,则事务B的更新就会由于需要等待事务C的提交而卡住(等待事务C释放id=1的行锁);

总结

  • RR隔离级别的核心是一致性读视图;

  • 事务在执行更新时需要执行当前读,如果当前有其他事务占据了行锁,则需要等待;

  • RR隔离级别在事务开始时就创建了一致性读视图,之后事务中的其他语句都共享该一致性读视图;

  • RC隔离级别在事务的第一个语句开始执行时才创建一致性读视图,之后事务中每条语句执行前都会重新计算一遍一致性读视图;

    • 在RC隔离级别下再分析一下示例;
    • 显然数据的版本依旧是101–> 102 –> 90;
    • 事务A在执行查询时的一致性视图为[99, 100, 101],低水位是99,高水位是103;则发现数据版本101在一致性视图数组中,且与当前事务ID不等,属于未提交事务,则不可见,继续找102版本,发现102版本不再一致性视图数组中,且小于高水位,属于已经提交的版本,可见,得到结果为2;
    • 事务B的查询时的一致性视图为[99, 100, 101], 低水位是99,高水位是103;则发现数据版本101在一致性视图数组中,且与当前事务ID相等,可见,得到结果为3;
  • 对于表结构,由于其没有对应的行数据,所以没有row trx_id字段,只能遵循当前读,也就是读最新版本;

如果对您有帮助