通过本文可以了解什么是事务。
事务
事务(Transaction)是逻辑上的一组操作,要么都执行,要么都不执行,一般指对数据库的操作。
事务的特性
在严格意义上,事务实现应该具备原子性、一致性、隔离性和持久性,简称ACID。
- 原子性(Atomicity):事务是最小的执行单位,确保在事务内所有操作要么全都执行,要么全不执行。
- 一致性(Consistency):指在事务开始之前和结束以后,数据应满足完整性约束,不会存在中间状态。比如A账户给B账户转钱,不管转账成功与否,A账户和B账户的总金额是不会变的。
- 隔离性(Isolation):在多个事务并发访问数据库时,事务之间是相互隔离的,不会被其他事务影响。
- 持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
事务的类型
从事务理论的角度来看,事务主要分为五大类,分别为扁平事务、带有保存点的扁平事务、链式事务、嵌套事务、分布式事务。
扁平事务(Flat Transactions)
扁平事务是事务类型中最简单且在实际生产环境中使用最频繁的事务。在数据库中,扁平事务通常由 begin 或 start transaction 开始,由 commit 或 rollback 结束,在这之间所有的操作,要么全部执行成功,要么全部执行失败回滚。当今主流的数据库都支持扁平事务。
扁平事务的缺陷在于无法提交或回滚整个事务中的部分事务,只能把整个事务全部提交或回滚。为了解决这个问题,就出现了带有保存点的扁平事务。
带有保存点的扁平事务(Flat Transactions with Savepoints)
带有保存点的扁平事务通过在事务内部某个位置设置保存点(savepoint),达到将当前事务回滚到此位置的目的。
本质上,普通的扁平事务也存在保存点,只不过只有一个隐式的保存点,并且这个保存点是在事务启动的时候,默认自动设置为当前事务的开始位置。
链式事务(Chained Transactions)
链式事务是在带有保存点的扁平事务的基础上,自动将当前事务的上下文隐式地传递给下一个事务。
也就是说,一个事务的提交操作和下一个事务的开始操作具有原子性,上一个事务的执行结果对下一个事务是可见的,事务与事务之间就像链条一样传递下去。
注意点:
链式事务在提交的时候,会释放要提交的事务中的所有的锁和保存点,也就是说链式事务的回滚操作只能回滚到当前事务所在的保存点,而不能回滚到已提交事务的保存点。
嵌套事务(Nested Transactions)
嵌套事务是指多个事务处于嵌套状态,共同完成一项任务,整个任务具备原子性。
嵌套事务最外层有一个顶层事务,这个顶层事务控制着所有的内部子事务,内部事务提交后,整体事务并不会提交,只有最外层事务提交后,整体事务才算提交完成。
注意点:
- 嵌套事务事务的提交是从内部的子事务向外依次进行的,直到顶层事务提交完成。
- 回滚嵌套事务内部的自事务时,会将整体事务回滚到顶层事务的开始位置。
- 回滚嵌套事务顶层事务时,会回滚嵌套事务包含的所有事务,包括已提交的内部子事务。
MySQL 不支持原生的嵌套事务,而 SQL Server 支持。
分布式事务(Distributed Transactions)
分布式事务指的是事务的参与者、事务所在的服务器、涉及的资源服务器及事务管理器等分别位于不同分布式系统的不同服务或者数据库节点上。简单的说分布式事务就是在不同环境下运行的整体事务,这个整体事务包含了一个或多个事务分支,并且整体事务中所有的事务分支要么全部提交成功,要么全部提交失败回滚。
本地事务
本地事务通常是通过关系型数据库本身的事务特性(一般会用日志+锁)来进行控制和实现的,而这种实现方式通常是数据库和应用会放在一台服务器上。
transaction begin
insert into ...
update xx set ...
delere from ...
transaction commit/rollback
本地事务的执行流程
- 客户端发起开启一个连接会话;
- 客户端发起开始事务的指令;
- 事务开启后,客户端发送 SQL 语句处理数据;
- 正常情况下,客户端发起提交事务的指令;异常情况下,客户端发起回滚事务的指令。
- 关闭会话。
本地事务的优缺点
优点
- 支持严格的 ACID 特性。
- 事务可靠,一般不会出现异常。
- 本地事务执行效率比较高。
- 事务的状态可以只在数据库中维护,上层应用不需要管理。
- 编程简单,不会涉及到复杂的网络通信问题。
缺点
- 一个事务过程中只能连接一个支持事务的数据库,而不能用于多个事务性数据库。
- 不具备分布事务处理能力。
并发事务带来的问题
脏写
描述
当两个或者两个以上的事务并行执行时,选择了同一行数据进行更新,因为事务之间无法互相感知,所以最后执行的事务操作的结果会覆盖之间由其他事务更新操作的结果。
举例
杰伦的账户余额有 100 元,当前有事务 A 和事务 B 两个事务,事务 A 是为其账户转账 100 元,事务 B 是为其账户转账 200 元。
事务 A 和事务 B 开启时,同时查询到杰伦的账户余额是 100 元,当事务 A 先于事务 B 提交,那么事务 B 的操作结果会覆盖事务 A 的操作结果,也就是账户余额变成了 300 元。
其中事务 A 的 100 元丢失了~
再如:
事务 A 和事务 B 开启时,同时查询到杰伦的账户余额是 100 元,当事务 B 先于事务 A 提交,此时事务 A 的由于异常导致回滚,也就是账户余额变成了 100 元。
其中事务 B 的 200 元丢失了~
本质
脏写的本质是写操作的冲突,解决方法是每个事务按串行的方式执行。
脏读
描述
一个事务在对数据进行修改且事务还未提交之前,另一个事务来读取正在修改的这条数据记录,如果没有对两个事务进行控制,第二个事务就会读到还未被提交的数据,并且对该数据进行进一步处理,此时就会产生未提交数据的依赖关系。
举例
杰伦的账户余额有 100 元,事务 A 为其账户转账 100 元,事务 A 还未提交,此时事务 B 来查询杰伦的账户余额为 200 元。这时由于某些异常原因导致事务 A 进行了回滚操作,事务 B 仍旧按查询到的账户余额 200 元进行其他数据处理,该查询到的数据就是脏数据。
本质
脏读的本质是读写操作的冲突,解决方法是先写后读。
不可重复读
描述
一个事务读取了某些数据,在一段时间后,这个事务再次读取之前读过的数据,此时发现读取的数据发生了变化。
举例
杰伦的账户余额有 100 元,当前有事务 A 和事务 B 两个事务,事务 A 是为其账户转账 100 元,事务 B 是查询其账户余额。
第一次查询时,事务 A 还没有进行转账,事务 B 查询到的账户余额是 100 元。第二次查询时,事务 A 已转账成功,此时事务 B 查到的账户余额是 200 元。两次的查询结果不一致。
本质
不可重复读的本质是读写操作的冲突,解决方法是先读后写。
幻读
描述
一个事务按照一定条件读取数据,期间另一个事务在相同条件范围内插入了新数据,当第一个事务再次查询时,发现另一个事务插入的新数据,两次查询的结果不一致。
举例
当前有事务 A 和事务 B 两个事务,事务 A 是两次查询杰伦的转账记录,事务 B 是为杰伦的账户转账 100 元。
事务 A 第一次查询时,事务 B 还未进行转账,事务 A 第二次查询时,事务 B 已转账成功,此时就会导致事务 A 两次查询的转账数据不一致。
本质
幻读的本质是读写操作的冲突,解决方法是先读后写。
总结
本节主要介绍了事务的特性、事务的类型、本地事务的基本概念和优缺点、并发事务带来的问题。
评论区