《凤凰架构》读书笔记 —— 本地事务如何实现原子性和持久性?

​ 事务处理几乎是每一个信息系统中都会涉及到的问题,它存在的意义就是保证系统中的数据是正确的,不同数据间不会产生矛盾,数据的修改后的结果使我们期望的,也就是保证数据状态的一致性(Consistency)

​ 想要达成数据库状态的一致性,需要三个方面的努力:

  • **原子性(Atomic):**在同一项业务处理过程中,事务保证了对多个数据的修改操作,要么同时成功,要么同时失败并撤销
  • **隔离性(Isolation):**在不同的业务处理过程中,事务保证了各自正在读写的数据相互独立,互不影响
  • **持久性(Durability):**事务应当保证所有被成功提交的数据修改都能被正确的持久化,不丢失数据

​ 这就是 ACID 的概念。 A I D 是手段, C 是目的。

本地事务

​ 本地事务是指仅操作特定单一事务资源的、不需要“全局事务管理器”进行协调的事务。

​ 本地事务是最基础的一种事务处理方案,通常只适用于单个服务使用单个数据源的场景,它是直接依赖于数据源(通常是数据库系统)本身的事务能力来工作的,事务的开启、终止、提交、回滚、嵌套、设置隔离级别、乃至与应用代码贴近的传播方式,全部都要依赖底层数据库的支持

​ 在程序代码层面,我们最多只能对事务接口做一层标准化的包装(如 JDBC 接口),并不能深入参与到事务的运作过程当中。

实现原子性和持久性

​ 实现原子性和持久性所面临的困难是,“写入磁盘”这个操作不会是原子的,不仅有“写入”与“未写入”,还客观地存在着“正在写”的中间状态。

Fenix’s Bookstore 是一个在线书店。一份商品成功售出,需要确保以下三件事情被正确地处理:

  1. 用户的账号扣减相应的商品款项;

  2. 商品仓库中扣减库存,将商品标识为待配送状态;

  3. 商家的账号增加相应的商品款项。

​ 由于写入存在中间状态,可能发生以下情形:

  • 未提交事务:程序还没修改完三个数据,数据库已经将其中一个或两个数据的变动写入了磁盘,此时出现崩溃,一旦重启之后,数据库必须要有办法得知崩溃前发生过一次不完整的购物操作,将已经修改过的数据从磁盘中恢复成没有改过的样子,以保证原子性
  • 已提交事务:程序已经修改完三个数据,数据库还未将全部三个数据的变动都写入到磁盘,此时出现崩溃,一旦重启之后,数据库必须要有办法得知崩溃前发生过一次完整的购物操作,将还没来得及写入磁盘的那部分数据重新写入,以保证持久性

​ 这种数据恢复操作被称为 崩溃恢复

​ 为了能够顺利地完成崩溃恢复,在磁盘中写数据就不能像程序修改内存中变量值那样,直接改变某表某行某列的某个值,必须将修改数据这个操作所需的全部信息(比如修改什么数据、数据物理上位于哪个内存页和磁盘块中、从什么值改成什么值等等),以日志的形式(日志特指仅进行顺序追加的文件写入方式,这是最高效的写入方式)先记录到磁盘中。

Commit Logging

​ 只有在日志记录全部都安全落盘,见到代表事务成功提交的 Commit Record 后,数据库才会根据日志上的信息对真正的数据进行修改,修改完成后,在日志中加入一条 End Record 表示事务已完成持久化,这种事务实现方法被称为 Commit Logging

​ 日志一旦成功写入 Commit Record ,那整个事务就成功了。及时修改数据时崩溃了,重启后根据这部分日志恢复现场、继续修改即可,这保证了持久性

​ 如果日志没有成功就发生崩溃,系统重启后会看到一部分没有 Commit Record 的日志,将这一部分日志回滚即可整个事务就像完全没有发生过一样,这保证了原子性

​ **Commit Logging 存在一个巨大的缺陷:**所有对数据的真实修改都必须发生在事务提交、日志写入了 Commit Record 之后,即使事务提交前磁盘 I/O 有足够空闲、即使某个事务修改的数据量非常庞大,占用大量的内存缓冲,无论何种理由,都决不允许在事务提交之前就开始修改磁盘上的数据,这一点对提升数据库的性能是很不利的。

​ 为了解决这个缺陷,前面提到的 ARIES 理论终于可以登场了。ARIES 提出了 “ Write-Ahead Logging ” 的日志改进方案,其名字里所谓的“提前写入”(Write-Ahead),就是允许在事务提交之前,提前写入变动数据的意思。

Write-Ahead Logging

Write-Ahead Logging 先将何时写入变动数据,按照事务提交时点为界,分为了 FORCE 和 STEAL 两类:

  • FORCE:当事务提交后,要求变动数据必须同时完成写入则称为 FORCE;如果不强制变动数据必须同时完成写入则称为 NO-FORCE。现实中绝大多数数据库采用的都是 NO-FORCE 策略,只要有了日志,变动数据随时可以持久化,从优化磁盘 I/O 性能考虑,没有必要强制数据写入立即进行。
  • STEAL:在事务提交前,允许变动数据提前写入则称为 STEAL,不允许则称为 NO-STEAL。从优化磁盘 I/O 性能考虑,允许数据提前写入,有利于利用空闲 I/O 资源,也有利于节省数据库缓存区的内存。

​ Commit Logging 允许 NO-FORCE,但不允许 STEAL。因为假如事务提交前就有部分变动数据写入磁盘,那一旦事务要回滚,或者发生了崩溃,这些提前写入的变动数据就都成了错误。

​ Write-Ahead Logging 允许 NO-FORCE,也允许 STEAL,它给出的解决办法是增加了另一种称为 Undo Log 的日志。当变动数据写入磁盘前,必须先记录 Undo Log,写明修改哪个位置的数据、从什么值改成什么值,以便在事务回滚或者崩溃恢复时,根据 Undo Log 对提前写入的数据变动进行擦除。

Undo Log 现在一般被翻译为**“回滚日志”**,此前记录的用于崩溃恢复时重演数据变动的日志,就相应被命名为 Redo Log,一般翻译为“重做日志”。

​ 由于 Undo Log 的加入,Write-Ahead Logging 在崩溃恢复时,会以此经历以下三个阶段:

  • 分析阶段(Analysis):该阶段的最后一次检查点(Checkpoint,可理解为在这个点之前所有应该持久化的变动都已安全落盘)开始扫描日志,找出所有没有 End Record 的事务,组成待恢复的事务集合
  • 重做阶段(Redo):该阶段依据分析阶段中,产生的待恢复的事务集合来重演历史(Repeat History),找出所有包含 Commit Record 的日志,将它们写入磁盘,写入完成后增加一条 End Record,然后移除出待恢复事务集合。
  • 回滚阶段(Undo):该阶段处理经过分析、重做阶段后剩余的恢复事务集合,此时剩下的都是需要回滚的事务(被称为 Loser),根据 Undo Log 中的信息回滚这些事务。

​ 重做阶段和回滚阶段的操作都应该设计为幂等的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值