Spring 提供统一的事务抽象,通过统一的编程模型使得应用程序可以很容易的在不同的事务框架之间进行切换。在了解Spring事务之前,我们先看看数据库是如何对事务进行管理的。
数据库事务概述
准确的讲事务是数据库区别于文件系统的重要特性之一,那么何为事务呐?构成单一逻辑工作单元的操作集合称作事务。简单来讲,事务就是确保在数据库提交工作时,要么所有修改都保存,要么所有修改都不保存,进而事务是把数据库从一种一致状态转换为另一种一致状态的程序执行单元。
事务必须同时满足四个特性,即我们通常所讲的事务的ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
原子性(Atomicity)
一个简单的数据操作会由一系列的动作组成,那么在实际操作过程中肯定不允许其中某几个动作成功,从而需要将这一系列动作封装成一个不可分割的工作单位,确保这一系列动作要么全部完成,要么完全不起作用。因此原子性就是指整个数据库事务是不可分割的工作单位。
一致性(Consistency)
一致性指事务将数据库从一中状态转变为下一种一致的状态。简单来讲,就是事务开始之前和事务结束以后,数据库中的完整性约束没有被破坏。比如,唯一索引,某字段在事务开始前是全表唯一的,事务结束以后该字段还会是全表唯一,否则就违背了一致性。
隔离式(Isolation)
隔离性要求并发执行过程中,每个读写事务的操作对其他事务的操作是不可见的,并且它们之间相互隔离、互不干扰,只有在当前事务提交以后对其它事务才可见。所以隔离式又被称做,“并发控制”。
持久性(Durability)
持久性就是确保事务一旦提交,其结果就会永久保存。即使发生系统故障,数据库也能恢复其故障丢失的数据。但是需要特别说明下,如是外部不可逆的原因造成的数据丢失,如硬盘挂掉,数据是无法恢复的。进而持久性保证的是事务层面的数据永久保存。
事务分类
事务又分为五类,扁平事务、带有保存点的扁平事务、链事务、嵌套事务和分布式事务。
扁平事务(Flat Transaction)
扁平事务是事务类型中最简单的一种,同时也使用最为频繁的一种。扁平事务中都由begin work开始事务,由commit work或rollback work结束事务,并且从begin到commit/rollback之间的所有操作都是原子性的,要么都执行、要么都回滚,因此扁平事务中所有的操作都处于同一层次中。
扁平事务中要求commit必须提交事务中所有的操作,rollback必须回滚事务中所有的操作,在这个大前提下就无法按需回滚到某个事务操作上,所以就出现了带有保存点的扁平事务。
带有保存点的扁平事务(Flat Transaction with Savepoint)
带有保存点的扁平事务除了支持扁平事务原本的操作之外,还允许在事务执行过程中回滚到同一事务中较早的一个状态。这样设计主要是因为在事务执行过程中,某些操作出现错误并不需要回滚所有的事务操作,同时也是为了节省事务开销。
可以在事务执行过程中传创建保存点,以便当以后发生错误时,事务能根据创建的保存点回滚到保存点那时的状态。在使用保存点需要注意,保存点在当前事务内部是全局递增的,就算在事务执行过程中触发了rollback也不会影响其计数。
begin;
INSERT INTO test.drs_change_log
(id, source, dist, remark, delete_flag, created_by, created_at, updated_by, updated_at, version)
VALUES(1392729332587216891, '123132', NULL, '执行删除逻辑', 0, NULL, '2021-05-13 14:32:03', NULL, '2021-05-13 14:32:03', 0);
# 设置保存点a
savepoint a;
select * from drs_change_log where id = 1392729332587216891;
INSERT INTO test.drs_change_log
(id, source, dist, remark, delete_flag, created_by, created_at, updated_by, updated_at, version)
VALUES(1392729332587216892, '123123', NULL, '执行删除逻辑', 0, NULL, '2021-05-13 14:32:03', NULL, '2021-05-13 14:32:03', 0);
select * from drs_change_log where id in (1392729332587216891, 1392729332587216892);
# 回滚到保存点a
rollback to savepoint a;
select * from drs_change_log where id in (1392729332587216891, 1392729332587216892);
# 提交事务
commit;
链事务(Chained Transaction)
链事务算是带有保存点事务的一种变种模式,因为带有保存点的扁平事务中的保存点是易失的,当系统发生崩溃时,所有的事务内所有的保存点都会随之消失。这样就会造成一个问题,一旦此时想要恢复事务,就只能从事务开始的位置进行恢复,链事务就由此诞生了。
那么链事务是如何处理的呐?在提交一个事务的时候,首先释放不需要的数据对象,然后再将必要的处理上下文隐式传给下一个要开始的事务。这里的提交和传递在一个原子操作中完成。这样一来下一个事务就能看见上一个事务的结果,恢复的时候就可以依此进行。
链事务和带有保存点的扁平事务相比最大的区别就是,带有保存点的扁平事务能恢复到任意保存点,而链事务只能恢复到最近的一个保存点。
嵌套事务(Nested Transaction)
嵌套事务是一个层次结构,一个顶层事务控制着各个层次的事务。顶层事务下嵌套的事务被称为子事务,具体控制着每一个局部的数据变换。我们亦可以直接将嵌套事务理解为树型结构,其顶层事务主要负责控制和管理旗下的嵌套子事务,具体的工作则交给子事务来完成,即只有子事务才能访问数据库。
分布式事务(Distributed Transactions)
分布式事务简单理解就是分布式环境下的扁平事务,因为在分布式环境下会需要根据数据所在的位置访问网络中的不同节点,各个节点都会自己的扁平事务,但是如若发生错误需要回滚事务,就必须将其所有的事务都回滚,否则就会出现大问题,这也是分布式事务出现的原因之一。
事务的隔离级别
数据库上有多个事务同时执行的时候,就会出现脏读、不可重复读和幻读的问题,为了解决这些问题就引入了隔离级别的概念。但是隔离级别越高,执行效率就越低。SQL的标准隔离级别有,读未提交、读提交、可重复读和串行化。
- 读未提交,当前事务还没提交时,事务中做的操作就可以被别的事务看到。
- 读提交,只有当前事务提交以后,事务中做的操作才对其它事务所见。
- 可重复读,当前事务在执行过程中无论何时看到的数据状态,都跟事务刚启动时看到的一样。
- 串行化,是最严格的隔离级别,会对写和读都加锁,如遇锁冲突必须等待锁释放才能继续操作。