文章目录
在数据库系统中,事务是保证数据一致性的关键机制之一。
事务处理遵循ACID原则,并且有不同的隔离级别来防止并发事务间的数据冲突。
此外,事务的传播性是指在多层调用中如何继承事务的行为。
一,ACID原则
ACID是Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的缩写,这是事务处理的基本原则。
-
原子性(Atomicity):
- 事务中的所有操作要么全部完成,要么全部不完成。如果事务的一部分成功而另一部分失败,则整个事务被视为失败,并且之前成功的部分也将被回滚。
-
一致性(Consistency):
- 事务开始前和结束后,数据库都必须处于一致性状态。这意味着事务不能破坏任何规则,如外键约束、唯一性约束等。
-
隔离性(Isolation):
- 多个并发执行的事务之间相互隔离,不会看到彼此未提交的结果。这是通过不同的隔离级别来实现的。
-
持久性(Durability):
- 一旦事务提交,其结果将是永久的,即使系统发生崩溃也不会丢失已提交的数据。
1. 原子性 (Atomicity)
场景描述:
- Alice 的账户余额为 $500。
- Bob 的账户余额为 $300。
- Alice 想要向 Bob 转账 $100。
原子性要求:
- 如果转账成功,Alice 的账户余额应减少 $100,Bob 的账户余额应增加 $100。
- 如果转账过程中发生任何错误,如系统崩溃或网络中断,那么两个账户的余额都不应该改变。
示例说明:
- 开始事务。
- 从 Alice 的账户中扣除 $100。
- 将 $100 加到 Bob 的账户中。
- 提交事务。
结果:
- 如果事务成功提交,则 Alice 的余额变为 $400,Bob 的余额变为 $400。
- 如果事务过程中发生错误,如网络中断,则两个账户的余额保持不变,即 Alice 的余额仍为 $500,Bob 的余额仍为 $300。
2. 一致性 (Consistency)
场景描述:
- Alice 的账户余额为 $500。
- Bob 的账户余额为 $300。
- Alice 想要向 Bob 转账 $100。
一致性要求:
- 转账前后的账户总金额不变。
- 转账完成后,账户余额必须满足所有业务规则,如最低余额限制。
示例说明:
- 确保转账前 Alice 的账户余额大于等于 $100。
- 转账过程中,确保 Alice 和 Bob 的账户余额符合业务规则。
- 转账完成后,Alice 的账户余额为 $400,Bob 的账户余额为 $400,总金额仍为 $800。
结果:
- 如果转账过程中发现 Alice 的账户余额不足,则事务回滚,保持一致性。
- 如果转账成功,则确保 Alice 和 Bob 的账户余额均符合规则。
3. 隔离性 (Isolation)
场景描述:
- Alice 和 Bob 的账户余额分别为 $500 和 $300。
- 另一个事务尝试从 Bob 的账户中提取 $200。
- 同时,Alice 正在向 Bob 转账 $100。
隔离性要求:
- Alice 的转账事务和 Bob 的提现事务应该互相隔离,不能看到对方未提交的变更。
- 即使两个事务并发执行,结果也应该如同它们串行执行一样。
示例说明:
- Alice 开始事务,尝试从自己的账户中扣除 $100 并增加 Bob 的账户余额。
- Bob 开始事务,尝试从自己的账户中提取 $200。
- Alice 的事务提交。
- Bob 的事务尝试提交时,发现自己的账户余额不足以提取 $200,因此回滚。
结果:
- Alice 的账户余额变为 $400。
- Bob 的账户余额保持 $300 不变,因为 Bob 的事务未能提交。
- 即使两个事务并发执行,Bob 的账户余额也不会被错误地减少。
4. 持久性 (Durability)
场景描述:
- Alice 的账户余额为 $500。
- Bob 的账户余额为 $300。
- Alice 想要向 Bob 转账 $100。
持久性要求:
- 一旦事务提交,对数据库的更改就是永久的。
- 即使系统发生故障,如停电或重启,更改也应保持不变。
示例说明:
- Alice 开始事务,尝试从自己的账户中扣除 $100 并增加 Bob 的账户余额。
- Alice 的事务提交。
- 系统突然重启。
结果:
- 即使系统重启,Alice 的账户余额仍为 $400,Bob 的账户余额为 $400。
- 数据库恢复后,Alice 和 Bob 的账户余额保持事务提交时的状态。
二,隔离级别
1,简介
为了实现事务的隔离性,数据库支持不同的隔离级别,以控制事务之间可能产生的并发副作用。
-
读未提交(Read Uncommitted):
- 最低的隔离级别,允许事务读取其他事务尚未提交的数据,可能导致脏读、不可重复读和幻读。
-
读已提交(Read Committed):
- 事务只能读取其他事务已提交的数据,避免了脏读,但仍可能发生不可重复读和幻读。
-
可重复读(Repeatable Read):
- 在同一个事务内多次读取同一行数据时,结果是相同的,即使在这期间有其他事务更新了该行数据。这避免了不可重复读,但仍然可能发生幻读。
-
序列化(Serializable):
- 最高的隔离级别,完全消除了并发事务带来的问题,但可能会导致性能下降,因为事务可能需要等待其他事务完成才能执行。
2,举例说明
为了更好地理解不同的隔离级别,使用一个简单的银行转账场景作为例子。
假设有两个账户:Alice 和 Bob。Alice 想要向 Bob 转账 $100。
通过四个不同的隔离级别来展示它们是如何影响并发事务处理的:
- 读未提交 (Read Uncommitted)
- 读已提交 (Read Committed)
- 可重复读 (Repeatable Read)
- 序列化 (Serializable)
场景描述:
- 初始状态下,Alice 的账户余额为 $500。
- 初始状态下,Bob 的账户余额为 $300。
- Alice 想要向 Bob 转账 $100。
2.1 读未提交 (Read Uncommitted)
隔离级别:读未提交是最弱的隔离级别,它允许事务读取其他事务尚未提交的数据。
示例说明:
- 事务 T1 开始,尝试从 Alice 的账户中扣除 $100 并增加 Bob 的账户余额。
- 事务 T2 开始,读取 Bob 的账户余额,此时 Bob 的余额仍然是 $300(T1 还没有提交)。
- 事务 T1 完成,将 $100 加入 Bob 的账户并提交。
- 事务 T2 再次读取 Bob 的账户余额,此时 Bob 的余额已经是 $400。
结果:
- T2 两次读取 Bob 的账户余额得到不同的结果,这称为不可重复读。
- T2 在 T1 提交之前读取了 Bob 的账户余额,这称为脏读。
2.2 读已提交 (Read Committed)
隔离级别:读已提交禁止了脏读,但仍然允许不可重复读。
示例说明:
- 事务 T1 开始,尝试从 Alice 的账户中扣除 $100 并增加 Bob 的账户余额。
- 事务 T2 开始,读取 Bob 的账户余额,此时 Bob 的余额为 $300(T1 还没有提交)。
- 事务 T1 完成,将 $100 加入 Bob 的账户并提交。
- 事务 T2 再次读取 Bob 的账户余额,此时 Bob 的余额已经是 $400。
结果:
- T2 第一次读取 Bob 的账户余额为 $300,第二次为 $400,这同样是不可重复读。
- 但是,T2 只读取了已经提交的数据,所以没有脏读。
2.3 可重复读 (Repeatable Read)
隔离级别:可重复读禁止了脏读和不可重复读,但仍然允许幻读。
示例说明:
- 事务 T1 开始,尝试从 Alice 的账户中扣除 $100 并增加 Bob 的账户余额。
- 事务 T2 开始,在事务 T1 提交之前读取 Bob 的账户余额,此时 Bob 的余额为 $300。
- 事务 T1 完成,将 $100 加入 Bob 的账户并提交。
- 事务 T2 再次读取 Bob 的账户余额,此时 Bob 的余额仍然是 $300(由于可重复读特性)。
结果:
- T2 两次读取 Bob 的账户余额都是 $300,没有不可重复读。
- T2 读取的是事务 T1 提交之前的数据,所以没有脏读。
2.4 序列化 (Serializable)
隔离级别:序列化是最强的隔离级别,它完全消除了脏读、不可重复读和幻读。
示例说明:
- 事务 T1 开始,尝试从 Alice 的账户中扣除 $100 并增加 Bob 的账户余额。
- 事务 T2 开始,尝试读取 Bob 的账户余额,但由于 T1 正在进行,T2 必须等待 T1 提交。
- 事务 T1 完成,将 $100 加入 Bob 的账户并提交。
- 事务 T2 继续,读取 Bob 的账户余额,此时 Bob 的余额为 $400。
结果:
- T2 读取的数据与 T1 提交之后的数据一致,没有脏读、不可重复读和幻读。
- T2 必须等待 T1 完成,这可能会导致性能降低。
三,事务的传播性
事务的传播性是指在一个事务方法中调用另一个事务方法时,如何处理事务边界的问题。Spring框架中定义了几种传播行为:
-
REQUIRED:
- 如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新的事务。
-
SUPPORTS:
- 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务方式执行。
-
MANDATORY:
- 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
-
REQUIRES_NEW:
- 创建一个新的事务,如果当前存在事务,则挂起当前事务。
-
NOT_SUPPORTED:
- 以非事务方式执行操作,并挂起当前事务(如果有)。
-
NEVER:
- 以非事务方式执行,如果当前存在事务,则抛出异常。
-
NESTED:
- 如果当前存在事务,则在嵌套事务内执行;如果当前不存在事务,则行为类似于
REQUIRED
。
- 如果当前存在事务,则在嵌套事务内执行;如果当前不存在事务,则行为类似于
通过这些传播行为,可以灵活地控制多层调用中的事务行为,确保事务正确地传播和管理。