文章目录

  • 1、事务四大特性
  • 1.1、原子性
  • 1.2、一致性
  • 1.3、隔离性
  • 1.4、持久性
  • 2、并发事务带来问题
  • 2.1、脏读
  • 2.2、不可重复读
  • 2.3、幻读
  • 3、事务隔离级别
  • 3.1、读未提交
  • 3.2、读已提交
  • 3.3、可重复读
  • 3.4、串行化
  • 4、MVCC
  • 4.1、InnoDB隐藏字段
  • 4.2、undo log版本链
  • 4.3、ReadView
  • 4.4、MVCC工作流程
  • 4.5、可重复读 MVCC 实现
  • 4.6、读已提交 MVCC 实现


1、事务四大特性

对于Java开发的小伙伴来说,事务这个词并不陌生,当我们在业务代码中涉及到多个DML(非查询)操作时,一般都会使用事务,目的是防止业务操作中途报错,导致业务执行不完整,数据逻辑不正确,所以事务的作用主要是保证了数据的准确性,对于MySQL事务来说,具备原子性、一致性、隔离性、持久性这四大特性,接下来我们依次讲解。

1.1、原子性

原子性指的是:一个事务中的所有操作,要么全部成功,要么全部失败;这个事务特性是通过undo log日志来实现的。当事务执行过程中某个DML操作发生错误,就可以通过undo log的回滚链进行回滚操作,这样就恢复到事务操作之前的状态了,保证了原子性

1.2、一致性

一致性指的是:事务执行前后,数据库中的数据是一致的。例如:A用户和B用户各自有1000存款,A向B转500,最终A存款为500,B存款为1500;而不是A存款为500,B存款还是1000,这就叫事务的一致性。事务的其他三个特性最终保证了事物的一致性

1.3、隔离性

隔离性指的是:数据库中多个事务操作之间不会互相干扰,并发执行的事务之间是隔离的,当多个事务操作同个数据时,事物之间不会造成干扰和影响。对于隔离性是通过MVCC锁机制来实现的(MVCC后续会详细讲解)。

1.4、持久性

持久性指的是:实务操作提交完成之后,MySQL数据是持久化存储到硬盘中的。

2、并发事务带来问题

当多个客户端连接到MySQL数据库时,大概率也会出现同时开启事务操作的场景,一般称之为并发事务;对于并发事务场景来说,就可能出现脏读不可重复读幻读问题,接下来以此解释一下并发事务所带来的这三种情况。

2.1、脏读

脏读指的是:事务A读取到了事务B修改了但未提交的数据。

mysql的事务id是什么时候申请的_mysql的事务id是什么时候申请的

2.2、不可重复读

不可重复读指的是:事务A读取到了事务B修改了并提交的数据。

mysql的事务id是什么时候申请的_mysql的事务id是什么时候申请的_02

2.3、幻读

幻读指的是:事务A前后两次查询的数据条数不一样。

mysql的事务id是什么时候申请的_ACID_03

3、事务隔离级别

3.1、读未提交

读未提交指的是:事务A能够读取到事务B修改了但未未提交的数据。跟上面提到的脏读现象是一样的,所以读未提交隔离级别无法避免脏读现象的发生。

3.2、读已提交

读已提交指的是:事务A能读取到事务B修改了并提交的数据。跟上面提到的不可重复读现象是一样的,所以读已提交隔离级别无法避免不可重复读现象的发生,但是可以避免脏读

3.3、可重复读

可重复读指的是:事务开启后,每次读取的数据都是一致的。这是事务默认的隔离级别。所以可重复读解决了脏读、不可重复读现象,但是不能完全避免幻读现象。

3.4、串行化

串行化指的是:单个事物对数据进行操作时,会上读写锁。这样其他事务需要等待该事务操作完毕释放锁后,才可以进行操作。所以串行化可以解决并发事务带来的所有问题,也就是脏读、不可重复读、幻读都能处理。

4、MVCC

MVCC又称为多版本并发控制,该机制是InnoDB存储引擎特有的,它解决了并发事务场景下的读写性能问题,完全不需要加锁。

只有读已提交可重复读这两种隔离级别具备MVCC,并且实现的细节也有些许不同,换句话说读已提交可重复读隔离级别实现是通过MVCC机制来保证的。至于读未提交允许事务A读取事务B未提交的记录,自然就不需要MVCC介入;至于串行化已经通过锁来限制事务的操作是串行的,更不需要MVCC来保障。

MVCC多版本并发控制需要undo log版本链ReadViewroll_pointer来合作实现,接下来我们依次介绍,深入了解一下MVCC是如何工作的。

4.1、InnoDB隐藏字段

对于InnoDB存储引擎来说,它会为MySQL表中每一行数据都设置默认的字段信息,主要有3个:

  • trx_id:当前事务操作id
  • row_id:隐式主键,如果某行记录不存在主键字段,就会生成row_id代替
  • roll_pointer:回滚指针,用于记录undo log回滚地址

mysql的事务id是什么时候申请的_mysql_04

4.2、undo log版本链

undo log记录是用于事务进行回滚操作的,undo log记录通过roll_pointer地址进行连接、回滚,下图中的整个链路就可以理解为undo log版本链,当事务操作需要发生回滚事,就是通过undo log版本链恢复到事务前的状态。

mysql的事务id是什么时候申请的_事务_05

4.3、ReadView

当事务启动后,MVCC会为查询操作生成ReadView(类似当前数据快照),不过在读已提交可重复读这两种隔离级别,ReadView生成的细节也是不同的(4.5、4.6会详细说说),ReadView组成分为以下四个部分:

  • creator_trx_id:创建该ReadView的事务ID
  • trx_ids:表示在生成当前ReadView时,系统内活跃未提交的事务ID列表
  • min_trx_id:活跃的事务列表中,最小的事务ID
  • max_trx_id:表示在生成当前ReadView时,数据库将要给下个事务分配的ID
4.4、MVCC工作流程

上面提到过,MVCC的工作需要undo log版本链ReadViewroll_pointer来合作实现,接下来我们来看看到底是如何配合完成MVCC。

InnoDB会为每一行记录设置隐藏字段trx_idroll_pointer,表示当前记录的操作事务id回滚指针,undo log记录通过roll_pointer进行连接,构成了undo log版本链。那么此时某个事务的查询操作能看到怎样的结果,要符合以下的规则:

  • 记录 trx_id < ReadView 中 min_trx_id:表示这个版本记录是在创建 ReadView 前提交的事务生成的,所以该版本记录对当前事务可见
  • 记录 trx_id ≥ ReadView 中 max_trx_id:表示这个版本记录是在创建 ReadView 后启动的事务生成的,所以该版本记录对当前事务不可见
  • 记录 trx_id 在 max_trx_id 和 min_trx_id 之间:
  • 记录 trx_id 在 m_ids 中,表示生成该版本记录的活跃事务未提交,该版本的记录对当前事务不可见
  • 记录 trx_id 不在 m_ids 中,表示生成该版本记录的活跃事务已提交,该版本的记录对当前事务可见
4.5、可重复读 MVCC 实现

对于可重复读来说,事务首次select操作前会生成一个ReadView,整个事务操作阶段范围内都会使用这个ReadView

假设现在数据库中有一行记录如下:

mysql的事务id是什么时候申请的_事务_06


此时开启事务A和事务B,分别如下:

mysql的事务id是什么时候申请的_ACID_07


此时可以看到,由于记录的事务id为1,不论是事务A还是事务B的事务id都是 > 记录的trx_id,所以事务A和事务B都能够读取到这行数据。假设此时事务A将本行记录进行了修改,记录如下:

mysql的事务id是什么时候申请的_mysql的事务id是什么时候申请的_08


此时事务B再次读取记录信息时,就会发现这条记录的事务id为2,在事务B的ReadViewtrx_ids中,说明该记录是活跃的未提交的,那么本次改动对于事务B来说就是不可见的,所以还要顺着undo log版本链找到下一条记录,发现trx_id为1,说明是可见的。

所以可重复读隔离级别通过MVCC解决了不可重复读问题:事务B读取不到事务A修改并提交的数据。

4.6、读已提交 MVCC 实现

对于读已提交来说,事务每次select操作前会生成一个ReadView,后续每次的查询操作都使用最新的ReadView

针对于4.5章节提到那个情况,事务A将数据进行了修改,如下:

mysql的事务id是什么时候申请的_mysql的事务id是什么时候申请的_08


由于读已提交导致事务B每次读数据都会重创建新的ReadView,如下:

mysql的事务id是什么时候申请的_事务_10


此时可以发现事务A修改提交后的记录trx_id < 事务B的新ReadViewmin_trx_id,所以对于事务B来说就可以看见事务A修改并提交的数据,由于每次查询操作都会创建最新的ReadView,在事务操作期间其他事务提交记录也能够读取到。