文章目录
- 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修改了但未提交的数据。
2.2、不可重复读
不可重复读
指的是:事务A读取到了事务B修改了并提交的数据。
2.3、幻读
幻读
指的是:事务A前后两次查询的数据条数不一样。
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版本链
、ReadView
、roll_pointer
来合作实现,接下来我们依次介绍,深入了解一下MVCC是如何工作的。
4.1、InnoDB隐藏字段
对于InnoDB存储引擎来说,它会为MySQL表中每一行数据都设置默认的字段信息,主要有3个:
- trx_id:当前事务操作id
- row_id:隐式主键,如果某行记录不存在主键字段,就会生成row_id代替
- roll_pointer:回滚指针,用于记录undo log回滚地址
4.2、undo log版本链
undo log
记录是用于事务进行回滚操作的,undo log记录
通过roll_pointer
地址进行连接、回滚,下图中的整个链路就可以理解为undo log版本链,当事务操作需要发生回滚事,就是通过undo log版本链
恢复到事务前的状态。
4.3、ReadView
当事务启动后,MVCC
会为查询操作生成ReadView
(类似当前数据快照),不过在读已提交
和可重复读
这两种隔离级别,ReadView生成的细节也是不同的(4.5、4.6会详细说说),ReadView
组成分为以下四个部分:
creator_trx_id
:创建该ReadView的事务IDtrx_ids
:表示在生成当前ReadView时,系统内活跃未提交的事务ID列表min_trx_id
:活跃的事务列表中,最小的事务IDmax_trx_id
:表示在生成当前ReadView时,数据库将要给下个事务分配的ID
4.4、MVCC工作流程
上面提到过,MVCC的工作需要undo log版本链
、ReadView
、roll_pointer
来合作实现,接下来我们来看看到底是如何配合完成MVCC。
InnoDB会为每一行记录设置隐藏字段trx_id
和roll_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
。
假设现在数据库中有一行记录如下:
此时开启事务A和事务B,分别如下:
此时可以看到,由于记录的事务id为1,不论是事务A还是事务B的事务id都是 > 记录的trx_id,所以事务A和事务B都能够读取到这行数据。假设此时事务A将本行记录进行了修改,记录如下:
此时事务B再次读取记录信息时,就会发现这条记录的事务id为2,在事务B的ReadView
的trx_ids
中,说明该记录是活跃的未提交的,那么本次改动对于事务B来说就是不可见
的,所以还要顺着undo log版本链找到下一条记录,发现trx_id
为1,说明是可见的。
所以可重复读
隔离级别通过MVCC
解决了不可重复读
问题:事务B读取不到事务A修改并提交的数据。
4.6、读已提交 MVCC 实现
对于读已提交
来说,事务每次
select操作前会生成一个ReadView
,后续每次的查询操作都使用最新的ReadView
。
针对于4.5章节提到那个情况,事务A将数据进行了修改,如下:
由于读已提交
导致事务B每次读数据都会重创建新的ReadView
,如下:
此时可以发现事务A修改提交后的记录trx_id < 事务B的新ReadView
的min_trx_id
,所以对于事务B来说就可以看见事务A修改并提交的数据,由于每次查询操作都会创建最新的ReadView
,在事务操作期间其他事务提交记录也能够读取到。