事务管理
一、什么是事务
在计算机系统中,特别是数据库系统中,事务是一系列严谨执行的逻辑操作单元。这些操作要么全部成功执行,要么全部不执行。它是一个不可分割的工作单位,即使这系列操作由多个独立的步骤组成,从外部看来,这一系列操作也如同一个单一的操作。
举个生活中的例子:
银行转账是一个典型的事务。假设A要转100元给B:
- 从A账户扣除100元。
- 向B账户增加100元。
这两个操作必须要么都成功,要么都失败。
- 都成功: A账户减少100元,B账户增加100元,转账完成。
- 都失败: 如果在扣除A账户的钱后,系统崩溃导致无法给B账户增加钱,那么A账户扣除的100元必须回滚(退回到A账户),确保账户总金额不变,转账失败。
绝不能出现A账户扣了钱,B账户没收到钱,或者A账户没扣钱,B账户却收到了钱的情况。
二、为什么需要事务管理
事务管理的主要目的是为了确保数据的一致性 (Consistency) 和 完整性 (Integrity),尤其是在并发环境(多个用户或程序同时访问和修改数据)和可能发生系统故障的情况下。
- 数据一致性: 保证数据从一个一致的状态转变到另一个一致的状态。例如,在转账操作中,无论成功与否,银行系统的总资金应该保持不变。
- 并发控制: 当多个事务同时操作同一份数据时,事务管理可以防止数据混乱和错误,例如“脏读”、“不可重复读”、“幻读”等问题。
- 故障恢复: 当系统发生断电、崩溃等意外情况时,事务管理能够确保未完成的事务对数据的修改被撤销,已完成的事务对数据的修改被持久化,从而将数据恢复到一个一致的状态。
三、事务的四大特征/ACID特性
事务具有四个经典的特性,通常被称为 ACID 特性,这是衡量一个事务管理系统是否可靠的重要标准:
-
原子性 (Atomicity):
- 含义: 事务是一个不可分割的最小工作单元。事务中的所有操作要么全部提交成功,要么全部失败回滚。如果任何一个操作失败,整个事务都将回滚到最初状态,就像这个事务从未执行过一样。
- 重要性: 保证了操作的完整性,不会出现部分成功部分失败的中间状态。
-
一致性 (Consistency):
- 含义: 事务执行的结果必须使数据库从一个一致性状态转变到另一个一致性状态。一致性关注的是数据的业务规则和约束不被破坏。
- 重要性: 维护数据的正确性和业务逻辑的完整性。例如,银行转账后,双方账户的总金额应该保持不变(如果只考虑这两个账户)。数据库的约束(如主键、外键、唯一性约束、检查约束等)也必须得到满足。
-
隔离性 (Isolation):
- 含义: 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相影响。
- 重要性: 在并发环境下,防止多个事务同时操作数据时可能引发的问题,如脏读、不可重复读、幻读。数据库系统通过不同的隔离级别来实现不同程度的隔离性。
-
持久性 (Durability):
- 含义: 一旦事务成功提交,则其对数据库数据的修改就是永久性的,即使后续系统发生故障(如断电或系统崩溃)也不会丢失。
- 重要性: 保证了数据的可靠性。通常通过将事务日志(redo log, undo log)持久化到磁盘来实现。
四、事务的具体操作
事务的具体操作通常涉及到以下几个关键命令或阶段:
-
开始事务 (Begin Transaction / Start Transaction):
- 描述: 标记一个事务的开始。在此之后的数据库操作都将被视为该事务的一部分。
- SQL示例:
START TRANSACTION;
(以MySQL)
-
执行事务操作 (Executing Transaction Operations):
- 描述: 在事务开始后,执行一系列的数据库操作,如
INSERT
,UPDATE
,DELETE
,SELECT
等。这些操作在此时可能只是记录在事务日志中,或者在内存中进行修改,并不一定立即写入到物理磁盘。
- 描述: 在事务开始后,执行一系列的数据库操作,如
-
提交事务 (Commit Transaction):
- 描述: 如果事务中的所有操作都成功执行,并且业务逻辑允许,则提交事务。提交后,事务所做的所有数据修改将被永久保存到数据库中(满足持久性)。
- SQL示例:
COMMIT;
-
回滚事务 (Rollback Transaction):
- 描述: 如果在事务执行过程中发生任何错误(如SQL执行失败、业务逻辑校验失败、系统异常等),或者业务需要取消该事务,则回滚事务。回滚会将数据库恢复到该事务开始之前的状态,撤销该事务已经进行的所有修改(满足原子性)。
- SQL示例:
ROLLBACK;
-
设置保存点 (Savepoint - 可选):
- 描述: 在一个较长的事务中,可以设置一个或多个保存点。如果后续操作出现问题,可以选择回滚到某个指定的保存点,而不是回滚整个事务。这为事务提供了更细粒度的控制。
- SQL示例:
- 设置保存点:
SAVEPOINT point_name;
- 回滚到保存点:
ROLLBACK TO SAVEPOINT point_name;
- 设置保存点:
- 注意: 回滚到保存点后,之前的操作仍然是事务的一部分,可以选择继续执行其他操作后提交整个事务,或者回滚整个事务。
简单流程示例 (SQL):
SQL
|
|
五、事务的隔离级别
为了平衡并发性能和数据一致性的需求,数据库系统定义了不同的事务隔离级别。隔离级别越高,数据一致性越好,但并发性能可能会受到影响。常见的隔离级别从低到高包括:
-
读未提交 (Read Uncommitted):
- 现象: 允许一个事务读取另一个事务尚未提交的修改(脏数据)。
- 问题: 可能导致脏读 (Dirty Read)。
- 性能: 并发性能最高,但数据一致性最差。很少在实际应用中使用。
-
读已提交 (Read Committed):
- 现象: 一个事务只能读取到其他事务已经提交的数据。解决了脏读问题。
- 问题: 在同一个事务内,多次读取同一数据,可能会得到不同的结果,因为其他事务可能在此期间提交了对该数据的修改。这称为不可重复读 (Non-Repeatable Read)。
- 性能: 大多数数据库的默认隔离级别(如 Oracle, SQL Server, PostgreSQL)。
-
可重复读 (Repeatable Read):
- 现象: 保证在一个事务中多次读取同一数据时,结果始终一致。解决了不可重复读的问题。
- 问题: 在一个事务中,当多次按照某个范围条件读取数据时,可能会发现其他事务在此期间插入了新的、符合该范围条件的数据,导致后续读取时多出一些“幻影”行。这称为幻读 (Phantom Read)。
- 性能: MySQL InnoDB 存储引擎的默认隔离级别。它通过间隙锁 (Gap Lock) 在一定程度上解决了幻读问题。
-
可串行化 (Serializable):
- 现象: 强制事务串行执行,即一个接一个地执行,完全避免了脏读、不可重复读和幻读问题。
- 问题: 并发性能最低,因为事务之间不能并行执行。
- 性能: 数据一致性最高,但并发能力最差。在对数据一致性要求极高且并发量不大的场景下可能使用。
选择哪个隔离级别?
选择合适的隔离级别需要在数据一致性和系统并发性能之间进行权衡,根据具体的业务场景需求来决定。
六、@Transactional注释
除了数据库层面直接使用SQL命令管理事务外,在应用开发中,我们通常会使用框架或特定的API来管理事务,这样可以更方便地将事务管理逻辑与业务逻辑解耦。
@Transactional 的用法
-
Spring Framework 提供的一个注解,用于声明性地管理事务。它可以应用在类级别或方法级别。它会在方法运行之前,开启事务,运行完毕之后,根据运行结果,提交或回滚事务。
-
位置(业务Service层):
- 方法级别: 推荐使用。适用于一个业务方法多次对数据进行增、删、改等操作。当注解在方法上时,该方法将作为一个事务单元执行。
- 类级别: 当注解在类上时,表示该类中所有
public
方法都将默认应用相同的事务配置。如果方法上也有@Transactional
注解,则方法级别的配置会覆盖类级别的配置。 - 接口级别: 也可以注解在接口上,但不推荐,因为注解是不能被继承的,Spring 的事务是基于代理的,如果基于接口的代理(JDK动态代理),注解在接口上是有效的。但如果使用基于类的代理(CGLIB),注解在接口上是无效的。为了统一和避免混淆,建议注解在实现类或其方法上。
-
默认出现运行时异常(
RunTimeException
)才会回滚
前面我们通过spring事务管理注解@Transactional已经控制了业务层方法的事务。接下来我们要来详细的介绍一下@Transactional事务管理注解的使用细节。我们这里主要介绍@Transactional注解当中的两个常见的属性:
-
异常回滚的属性:
rollbackFor
-
事务传播行为:
propagation
我们先来学习下rollbackFor属性。
|
|
Spring 事务传播行为 (Propagation Behavior) 举例:
当一个事务方法调用另一个事务方法时,Spring 的事务传播行为定义了第二个事务方法如何参与到现有事务中,或者如何开启新事务。常见的传播行为有:
REQUIRED
(默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。SUPPORTS
: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。MANDATORY
: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。REQUIRES_NEW
: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。NOT_SUPPORTED
: 以非事务方式执行操作,如果当前存在事务,则把当前事务挂起。NEVER
: 以非事务方式执行,如果当前存在事务,则抛出异常。NESTED
: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED
类似的操作。嵌套事务是外部事务的一部分,有自己的保存点,可以独立回滚而不影响外部事务,但外部事务回滚会影响嵌套事务。
七、分布式事务 - 进阶
当一个业务操作需要跨多个独立的数据库或服务时,就涉及到分布式事务。单个数据库的ACID特性无法直接保证跨多个资源的事务一致性。
- 挑战:
- 多个参与者(数据库、消息队列等)的一致性难以保证。
- 网络延迟和故障增加了复杂性。
- 常见解决方案:
- 两阶段提交 (2PC - Two-Phase Commit):
- 准备阶段 (Prepare Phase): 协调者询问所有参与者是否可以提交事务。参与者执行事务操作,锁定资源,但不实际提交,并向协调者报告准备就绪或失败。
- 提交/回滚阶段 (Commit/Rollback Phase): 如果所有参与者都准备就绪,协调者通知所有参与者提交事务;否则,通知所有参与者回滚事务。
- 缺点: 同步阻塞、单点故障(协调者)、数据不一致(协调者宕机时)。
- 三阶段提交 (3PC - Three-Phase Commit): 2PC的改进,引入了超时机制和CanCommit阶段,减少了阻塞时间,但更复杂。
- TCC (Try-Confirm-Cancel): 补偿型事务。
- Try: 尝试执行业务,预留资源。
- Confirm: 如果Try阶段所有参与者都成功,则执行Confirm操作,实际完成业务。
- Cancel: 如果Try阶段有任何参与者失败,或者Confirm阶段某个参与者失败,则执行Cancel操作,回滚Try阶段预留的资源。
- 特点: 对业务代码侵入性强,需要业务层面实现Try, Confirm, Cancel三个接口。
- Saga (Saga Pattern): 长事务解决方案。将一个大事务拆分成多个本地事务,每个本地事务都有对应的补偿操作。如果某个本地事务失败,则按相反顺序执行前面已成功事务的补偿操作。
- 基于消息队列的最终一致性: 通过消息队列异步通知相关服务执行操作,并通过重试、幂等性、补偿等机制最终达到数据一致性。
- 两阶段提交 (2PC - Two-Phase Commit):
总结
事务管理是确保数据完整性和一致性的核心机制。理解其ACID特性、不同的隔离级别以及如何在应用中进行管理(声明式与编程式)是非常重要的。对于复杂系统,还需要考虑到分布式事务的挑战和解决方案。在实际开发中,我们会根据业务需求、性能要求和系统复杂度来选择合适的事务管理策略和技术。
希望以上回答能够清晰地解释事务管理的相关知识点。这样的回答结构清晰,内容详尽,应该能够给面试官留下一个好印象。