MySQL事物实战篇

文章目录

  • MySQL事物实战篇
      • 1. 案例一
      • 2. 案例一结果分析
      • 3. 一致性视图原理
      • 3. 结论

MySQL事物实战篇

前面一篇博客已经了解了事物的一些特征,这里先来看如下的一个执行流程:

1. 案例一

先创建表:

mysql> create table k (
    -> id int primary key,
    -> k int(11) not null)
    -> engine = innodb;
Query OK, 0 rows affected (0.36 sec)

mysql> insert into k values((1,1),(2,2));![1569206186765](C:\Users\Laptop\AppData\Local\Temp\1569206186765.png)

MySQL事物实战篇_第1张图片

执行上表的操作,这里先注意一下:

  • start transaction with consistent snapshot代表马上启动一个事物,begin/start transaction 命令并不是一个事物的起点,在执行到他们之后的第一个操作InnoDB表的语句,事物才真正启动;
    两者的区别:

    • 第一种启动方式:一致性视图是在执行start transaction with consistent snapshot时创建的;
    • 第二种启动方式:一致性视图是在执行第一个快照读语句时创建的;
    • 即当事物一用第一种方式启动后,在事物二更新一行数据后,在事物一中查询的结果是没更新的那个结果,而假如事物一用第二种方式启动的,则在事物一未执行任何操作前,事物二更新数据后,再在事物一种执行查询语句会得到更新后的结果;
  • 在C中没有显示的使用start来启动事物,但update t set k=k+1 where id = 1就是一个事物,执行完这条语句就会自动提交;(数据库中默认的autocommit为on,所以每个语句都是一个事物

    mysql> show variables like 'autocommit';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | autocommit    | ON    |
    +---------------+-------+
    1 row in set, 1 warning (0.10 sec)
    

在MySQL中,有两个视图的概念:

  • (1)一个是view,它是用于查询语句定义的虚拟表,在调用的时候执行查询语句并返回结果;
  • (2)另一个是InnoDB在实现MVCC时用到的一致性视图(read-view),即consistent read view,用于支持RC(读提交)和RR(可重复读)隔离级别的实现; 它没有物理结构,作用是事物执行期间用来定义“我能看到什么数据”;

2. 案例一结果分析

对于上面的执行(隔离级别为RR),事物A的查询结果是k=1;
事物B的查询为k=3;

这里对于事物A的查询结果一般不会有疑问,但对于B,为什么k会为3不是2呢?事物C的更新不是在事物B启动之后吗,不应该对B不可见吗?
这里就要注意一个东西,如果此时B的更新不在最新的值上进行,事物C的更新就会丢失,因此,事物B此时set k = k+1是在(1,2)的基础上进行的操作;

总结以上得到如下规律:

  • 更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”;
  • 可重复读的核心就是一致性读,当事物更新数据的时候,只能用当前读,如果当前的记录的行锁被其他事物占用的话(即其他事物也更新了这一行且还未提交),就要进入锁等待,即阻塞在这;

对于上面的案例,假如当前事物的隔离级别是读提交,那么结果又会是什么?

事物A查询语句返回的是k=2;因为在事物A查询的时候,事物B还未提交,所以不可见它的那行更新,而事物C的更新在A查询前就提交了,自然是可见的;
事物B的查询结果为3;

而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

  • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
  • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

3. 一致性视图原理

在可重复读的隔离级别下,事物在启动的时候就拍了个快照,这个快照是基于整个数据库的;

可能你会觉得拍了一个整个数据库的快照有点不现实,如果一个数据库很大(100G),岂不是启动一个事物就得拷贝所有的数据(100G)出来,这得有多慢啊,但事实上事物执行起来却很快呀,这到底是为什么呢?

事实上,我们并不需要拷贝出整个数据库的数据,先来看看这个快照是如何实现的;

InnoDB里面每个事物有一个唯一的事物ID,叫做transaction id,它是在事物开始的时候向数据库申请的,是按申请顺序严格递增的;

在数据库中,表中的每行数据也都是有多个版本的,每次事物更新的时候,就会生成一个新的数据版本,并把当前事物的transaction id赋值给这个数据版本的事物ID,记为row trx_id;同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它;

下面这张图,就是一个记录被多个事物连续更新后的状态:

MySQL事物实战篇_第2张图片
上图中最新的版本是V4,图中的虚箭头就是一个undo log(回滚日志) ,图中V1、V2、V3的值都不是真实存在的,都需要根据最新的值和回滚日志来进行计算;

回滚日志(undo log):在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作,记录上的最新值都可以通过回滚操作来计算出前一个状态的值,undo log记录的是逻辑日志,可以认为delete一条记录的时候,回滚日志会记录对应的insert操作,当update一条记录时,回滚日志会记录对应相反的update操作,当没有事物需要用到这些回滚日志的时候,回滚日志就会删除,对应系统里没有比这个回滚日志更早的read-view的时候就会删除对应的undo log;

上面介绍了多版本和row trx_id的概念,这时我们再来分析InnoDB是如何定义那“100G”的快照的:
有了上面的概念,对于可重复读这个隔离级别,一个事物在启动的时候只要声明:“以我启动的时刻为准,如果一个数据版本是在我启动之前生成的,就可见,如果是我启动以后才生成的,就不可见,我必须找到它的上一个版本,知道可见的那个版本为止”;

在实现上, InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事物 ID“活跃”指的就是,启动了但还没提交数组里面事务 ID 的最小值记为低水位当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)而数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到的

MySQL事物实战篇_第3张图片

一个数据的版本row trx_id可以有三种情况:

  • 如果在绿色部分,表示这个版本是已提交的事物或者是当前事物自己生成的,这个数据是可见的;
  • 如果落在红色部分,表示这个版本是由将来启动的事物生成的,当然是不可见的;
  • 如果落在黄色部分,就分为两种情况:
    • 若row trx_id在数组中,表示这个版本是还没有提交的事物生成的,不可见;
    • 若row trx_id不在数组中,表示这个版本是已经提交的事物生成的,可见;

3. 结论

InnoDB 的行数据有多个版本,每个数据版本有自己的 row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性。 对于可重复读,查只承认在事务启动前就已经提交完成的数据; 对于读提交,查询只承认在语句启动前就已经提交完成的数据; 而当前读,总是读取已经提交完成的最新版本。 你也可以想一下,为什么表结构不支持“可重复读”?这是因为表结构没有对应的行数据,也没有 row trx_id,因此只能遵循当前读的逻辑。 当然,MySQL 8.0 已经可以把表结构放在 InnoDB 字典里了,也许以后会支持表结构的可重复读。

你可能感兴趣的:(MySQL)