Spring Boot中的事务(新手必看)
数据库事务是构成单一逻辑工作单元的操作集合。
数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体。这个逻辑整体中的数据库操作,要么全部执行成功,要么全部不执行。
也就是说,构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,不管事务是否执行成功,数据库总是保持一致性状态。
1) 原子性。事务中的所有操作作为一个整体,像原子一样不可分割,要么全部成功,要么全部失败。
2) 一致性。事务的执行结果必须使数据库从一个一致性的状态到另一个一致性的状态。一致性状态是指系统的状态满足数据完整性约束(主码、参照完整性、check约束等),并且系统的状态反应数据库本应描述的显示的真实状态,比如银行转账之后,互相转账的两个账户金额总和保持不变。
3) 隔离性。并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
4) 持久性。事务一旦提交,其对数据库的更新就是持久的,任何事务或系统故障都不会导致数据丢失,不会因为系统故障或者断电造成数据不一致或者丢失。
数据库的并发问题可以大致归结为 4 类:脏读、不可重复读、幻读、丢失更新。
在这个场景中,取款事务 B 希望从账户中取出 200 元现金,与此同时转账事务 A 向账户中汇款 100 元,因为转账事务 A 在汇款之前读取了 B 事务尚未提交的数据,产生了脏读,导致账户丢失了 200 元。
2) 不可重复读。一个事务中两次(不同时间点)读取同一行数据,但是这两次读取到的数据不一致,如下表所示。
在这个场景中,事务 B 在事务取款前后读取的账户余额不一致,T4 节点查询的余额为 500 元,T7 节点查询的余额为 300 元。
在这个场景中,我们会发现当事务 A 和事务 B 都结束后数据库表会存在一条学生成绩信息是具体分数的形式,而非 ABCDEF 等级制的形式,此时就出现了幻读(见下表)。
① 第一类丢失更新,如下表所示。
在这个场景中,我们会发现事务 A 发生回滚之后事务 B 的操作丢失了,这种数据丢失会导致严重的问题,比如上述场景中个人账户就损失了 100 元。
② 第二类丢失更新,如下表所示:
在这个场景中,我们会发现事务 A 提交之后事务 B 的操作丢失了。这种数据丢失也会导致严重的问题,比如上述场景银行层面损失 100 元。
通过以上场景对比,我们会发现幻读和不可重复读很类似,都是读取到不一致的数据,当然本质上也是有区别的。幻读的侧重点在于插入和删除,即第二次查询数据会比第一次查询的数据变多了或者变少了。不可重复读的侧重点在于修改,即会出现第二次查询与第一次查询的同一条记录中某些字段值不一致的情况。
然而,完全的隔离性会导致系统的并发性很低,从而降低了资源的利用。所以,数据库对事务隔离性的要求有所放宽,从而在一定程度上造成了数据库数据的不一致性。
SQL 标准为事务定义了不同的隔离级别,从低到高依次是:
四种隔离级别易导致的并发异常如下表所示。
当然,每个数据库的默认隔离级别是不一样的。常用的关系型数据库 MySQL、Oracle、SQL Server 支持的隔离级别以及默认的隔离级别如下表所示。
数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体。这个逻辑整体中的数据库操作,要么全部执行成功,要么全部不执行。
也就是说,构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,不管事务是否执行成功,数据库总是保持一致性状态。
事务的特性
事务具有 4 个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),通常简称为 ACID。1) 原子性。事务中的所有操作作为一个整体,像原子一样不可分割,要么全部成功,要么全部失败。
2) 一致性。事务的执行结果必须使数据库从一个一致性的状态到另一个一致性的状态。一致性状态是指系统的状态满足数据完整性约束(主码、参照完整性、check约束等),并且系统的状态反应数据库本应描述的显示的真实状态,比如银行转账之后,互相转账的两个账户金额总和保持不变。
3) 隔离性。并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
4) 持久性。事务一旦提交,其对数据库的更新就是持久的,任何事务或系统故障都不会导致数据丢失,不会因为系统故障或者断电造成数据不一致或者丢失。
事务的并发问题
一个数据库中的同一份数据,由于被多客户端并发式访问,或者被多线程并发式访问,会导致多个事务同时访问同一份数据,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。数据库的并发问题可以大致归结为 4 类:脏读、不可重复读、幻读、丢失更新。
1) 脏读
事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据,如下表所示。
表:脏读例子

在这个场景中,取款事务 B 希望从账户中取出 200 元现金,与此同时转账事务 A 向账户中汇款 100 元,因为转账事务 A 在汇款之前读取了 B 事务尚未提交的数据,产生了脏读,导致账户丢失了 200 元。
2) 不可重复读。一个事务中两次(不同时间点)读取同一行数据,但是这两次读取到的数据不一致,如下表所示。
表:不可重复读例子

在这个场景中,事务 B 在事务取款前后读取的账户余额不一致,T4 节点查询的余额为 500 元,T7 节点查询的余额为 300 元。
3) 幻读
事务 A 表示系统管理员将数据库中某张存放成绩表的所有成绩都更新为 ABCDEF 等级,此时事务 B 插入了一条具体分数的记录,当事务 A 结束之后发现有一条数据学生成绩没有更新为 ABCDEF 等级制的,就好像发生了幻觉一样,此种情况称为幻读。在这个场景中,我们会发现当事务 A 和事务 B 都结束后数据库表会存在一条学生成绩信息是具体分数的形式,而非 ABCDEF 等级制的形式,此时就出现了幻读(见下表)。
表:幻读例子

4) 丢失更新
丢失更新又可细分为第一类丢失更新和第二类丢失更新。事务 A 覆盖了事务 B 已提交的更新数据,导致事务 B 的更新数据好像丢失了一样,称为丢失更新。① 第一类丢失更新,如下表所示。
表:第一类丢失更新例子

在这个场景中,我们会发现事务 A 发生回滚之后事务 B 的操作丢失了,这种数据丢失会导致严重的问题,比如上述场景中个人账户就损失了 100 元。
② 第二类丢失更新,如下表所示:
表:第二类丢失更新例子

在这个场景中,我们会发现事务 A 提交之后事务 B 的操作丢失了。这种数据丢失也会导致严重的问题,比如上述场景银行层面损失 100 元。
通过以上场景对比,我们会发现幻读和不可重复读很类似,都是读取到不一致的数据,当然本质上也是有区别的。幻读的侧重点在于插入和删除,即第二次查询数据会比第一次查询的数据变多了或者变少了。不可重复读的侧重点在于修改,即会出现第二次查询与第一次查询的同一条记录中某些字段值不一致的情况。
事务的隔离级别
事务具有隔离性,从理论上讲,事务与事务之间都是独立存在的,不应该互相影响;事务的执行也应该是完全串行化的,即先执行完事务 1,再执行事务 2,以此类推。然而,完全的隔离性会导致系统的并发性很低,从而降低了资源的利用。所以,数据库对事务隔离性的要求有所放宽,从而在一定程度上造成了数据库数据的不一致性。
SQL 标准为事务定义了不同的隔离级别,从低到高依次是:
- 读未提交的(ISOLATION_READ_UNCOMMITTED):最低的隔离级别,即事务未提交前就可以被其他事务读取,易导致幻读、脏读和不可重复读。
- 读已提交的(ISOLATION_READ_COMMITTED):表示一个事务提交之后才能被其他事务读取,即禁止其他事物读取到未提交的事务数据,易导致幻读和不可重复读。
- 可重复读(ISOLATION_REPEATABLE_READ):表示多次读取到同一个数据时,其值都与事务开始时读取到的内容一致,即禁止读取其他事务未提交的数据,可防止脏读和不可重复读,但易导致幻读。
- 序列化(ISOLATION_SERIALIZABLE):最可靠的隔离级别,也是最耗费效率、代价最大的隔离级别,该隔离级别可有效防止脏读、不可重复读和幻读。
四种隔离级别易导致的并发异常如下表所示。
表:四种隔离级别易导致的并发异常

当然,每个数据库的默认隔离级别是不一样的。常用的关系型数据库 MySQL、Oracle、SQL Server 支持的隔离级别以及默认的隔离级别如下表所示。
表:三种数据库支持的隔离级别及默认的隔离级别
