1.引言
做后端开发的同学,数据库是必备技能之一。我这里想说的必备技能,不单是说日常开发中我们写写CRUD增删改查的sql语句,我们还应该知道一些原理性的东西。比如说基本架构、事务、锁、索引等等。
这一篇我想跟你一起聊一聊事务、事务的基本特性,事务的实现原理,顺便跟你聊一聊回滚段、MVCC
2.案例
2.1.什么是事务
如果非要给事务下一个定义,不是很好描述。但是如果有人问你,用户购买商品下订单的时候,如何保证订单表,与库存表一致,即写入订单,与扣减库存要么都成功,要么都失败?
你一定会说,放到一个事务里执行啊,比如说这样
@Transactional
public void buy(){
// 1.写入订单表
// 2.扣减库存
}
你看,这就是事务,以及事务的作用。另外说到事务,我们必须要知道事务的四个基本特性
- 原子性(Atomicity):原子性,就是说事务是一个不可分割的基本单元,要么执行都成功,要么执行都失败
- 一致性(Consistent):一致性,就是说事务前后一致,比如说多个事务读取同一个数据结果一致
- 隔离性(Isolation):隔离性,就是说多个并发事务之间彼此独立,相互不影响
- 永久性(Durability):永久性,就是说事务一旦提交,它所做的修改将会持久化
在事务的四个基本特性中,原子性、一致性、永久性都比较好理解,隔离性相对来说理解起来稍微困难一些,毕竟在sql标准中定义了不同的隔离级别。
接下来我们继续看有哪些隔离级别,以及隔离级别的实现原理。
2.2.事务隔离级别
在sql标准中定义了四种隔离级别
- 读未提交(read uncommitted):一个事务,可以读取到其它事务未提交的数据,需要注意,读未提交会发生脏读
- 读提交(read commited):一个事务,只能读取到其它事务提交后的数据,需要注意,读提交会发生两次相同的读取,结果可能会不一致
- 可重复读(repeatable read):在同一个事务中,多次相同的读取,读取结果始终保持一致
- 串行化(serlalizable):加锁执行,写加写锁,读加读锁
我们知道,隔离级别从读未提交--->读提交--->可重复读--->串行化,隔离级别依次从低到高,隔离级别越高数据越安全,相对应的隔离级别越高并发度性能则越低。
因此,在实际应用中,选择哪一种隔离级别,我们需要结合业务特性来权衡。比如说在oracle数据库中,默认的隔离级别是读提交;在mysql数据库中,默认的隔离级别是可重复读
接下来我通过一个示例,让你可以更好的理解不同的隔离级别
- 假设有一张表t,t表中有一个整型字段c
- c的初始值为0
- 接下来有两个并发事务A、B,操作t表中的c字段,如图
这个时候我们的问题是,在不同的隔离级别下,事务A读取字段c的值V1、V2、V3分别取值是多少?
- 读未提交:读未提交是说,可以读取到其它事务未提交的数据,即最新的数据,那么此时V1的值是1,V2、V3的值都是1
- 读提交:读提交是说,只能读取其它事务提交后的数据,那么此时V1的值是0、V2、V3的值都是1
- 可重复读:可重复读是说,在同一个事务中,多次读取相同的数据,读取结果一致,那么此时V1、V2的值是0,V3的值是1
- 串行化:串行化是说,加锁避免并发,即先执行事务A,再执行事务B,在事务A执行过程中,事务B不能执行,那么此时V1、V2的值是0,V3的值是1
以上即是在不同的隔离级别下,V1、V2、V3读取结果的分析,应该说不难理解,如果你觉得稍微有些困难,请对照图中事务A、事务B的执行顺序多看几遍。
2.3.事务隔离级别实现原理
理解了不同的事务隔离级别,我们应该会有一个疑问,比如说可重复读,在同一个事务中,它是如何实现多次读取,读取结果一致的呢?哪怕数据已经被其它事务修改,并且提交了也不受到影响。
关于这个疑问,就是我们接下来要聊的事务实现原理,我从一个容易理解的角度,带着你一步一步来分析。
首先我们知道一个事实,在项目中应用事务的时候,会有两个终结动作
- 如果执行成功,则提交commit
- 如果执行失败,则回滚rollback
这里回滚是关键,一旦执行失败,事务需要回滚,就意味着需要恢复执行前的状态,同时也就意味着整个事务中执行的改变系统都记录了下来,这就是我们常说的回滚段。
还是上面的表t中的字段c的变化,假设字段c的值,从最初的0变成了1,从1变成了2,再从2变成了3,那么这一系列变化在系统中是什么样的呢?我们还是来看一个图
上面这个图例,即是当有多个事务并发操作字段c的时候,系统中字段c的真实形态。我们看到此时对于t表的字段c,同时存在了多个值,这也就是我们平常说的MVCC(多版本并发控制),即同一行记录,在系统中存在有多个版本。
分析清楚了回滚段、以及MVCC的存在,我们就不难理解事务隔离级别的实现原理了。其实就是借助了回滚段,每一个回滚段我们都称之为视图,一致性读视图。具体可以这么理解
- 读未提交:永远读取最新的数据,不需要,也没有读视图
- 读提交:在每一次执行sql的时候,创建读视图
- 可重复读:在每一次事务开始的时候,创建读视图
- 串行化:加锁互斥,不存在并发,不需要,也没有读视图
最后,关于事务我们就聊到这里了,你需要关心的知识点有事务的基本特性、事务隔离级别、以及隔离级别的实现原理,期望这篇文章能给你带来一些收获。