文章

数据库事务 AID->C 概念梳理

数据库事务 AID->C 概念梳理

关于事务的详细解释, 可以参考 《凤凰架构》 这本书, 书中从单体应用到分布式系统详细解释了事务的概念

一、事务的目标

确保系统中所有的数据全部符合期望, 相关联的数据不会产生矛盾, 即 数据状态的一致性 Consistency

关于 一致性 Consistency, 又能分为以下两点:

  • 内部一致性 (本地事务): 一个服务仅使用一个数据源, DB 内部的事务
  • 外部一致性 (架构问题): 分布式、微服务、多数据 DB 实例

二、达成目标 (一致性 Consistency) 的手段

数据库 ARIES 理论: 基于语义的恢复和隔离算法

  • 原子性 Atomic: 在同一个事务中, 事务保证对多数据修改, 要么同时成功要么同时失败
  • 隔离性 Isolation: 在不同的事务中, 事务保证了各业务正在读写的数据相互独立, 不会被彼此影响
  • 持久性 Durability: 事务应该保证所有成功被提交的数据修改, 都能够被正确的持久化, 不会丢失数据

原子性 Atomic + 持久性 Durability 的挑战: 数据写入磁盘的操作不是原子性的, 存在多种状态, 写入中、未写入、写入成功、失败

上述三点 AID 是用来达成目标 C 的手段, 平时在太多博客上看到很多人直接拿 ACID 来分析, 感觉这样并不是很准确, 后面我们可以养成习惯, 使用 AID->C

三、Commit Logging

以顺序追加文件的形式, 将数据写入到磁盘:

  1. 当日志记录全部安全落盘, 在日志中写入 “Commit Record”
  2. 当 DB 发现 “Commit Record” 后, 会根据日志上的信息修改数据
  3. 数据全部修改完毕后, 在日志中写入 “End Record” 来完成持久化

Commit Logging 是存在一定缺陷的, 它对数据的修改发生在 Commit 之后, 容易造成空闲 IO 浪费, 并且消耗内存和缓存过多, 代表的 DB 有阿里的 OceanBase

四、WAL (Write Ahead Logging)

从名字可以看出来, WAL 表示 “在日志前写入”, 即 WAL 能够允许事务在提交之前写入变动数据到存储。 按照事务提交的时间点, 将写入变动数据又分为了 ForceSteal 两种情况:

  • Force: 事务提交之后, 变动数据必须同时完成写入, 否则是 No Force
  • Steal: 事务提交之前, 允许数据提前写入, 否则是 No Steal

在实现 WAL 时, 绕不开下面两个 Log:

  • 回滚日志 UndoLog: 变动数据写入磁盘之前, 需要先记录到 UndoLog, 注明修改的数据 (擦除事务提交前, 已经提前写的)
  • 重做日志 RedoLog: 用于崩溃恢复时, 重演数据变动的日志 (完成崩溃时未来得及写完的)

WAL 允许 Force 也允许 Steal, 我们可以使用各种组合来实现 WAL (2x2=4), 其中 No Force + Steal 的性能最好, 但是对应的实现也是最复杂的

五、隔离性 Isolation

SQL 标准定义了四种事务隔离级别, 每一种级别都会影响数据库的并发性和一致性: 读未提交、读已提交、可重复读、可串行化隔离性的实现原理: 通过对事务的读写操作应用不同类型的锁(读锁, 写锁, 范围锁), 来达到不同级别的事务隔离性

隔离级别脏读不可重复读幻读说明案例
读未提交 (Read Uncommitted)允许允许允许仅仅对事务涉及到的数据加了写锁。可能会发生事务 T1 读到了事务 T2 未提交的数据发生幻读(缺乏范围锁) + 不可重复读(缺乏贯穿 T1 的读锁) + 脏读(T1 读到了 T2 未提交的事务, 缺乏读锁)
读已提交 (Read Committed)禁止允许允许对事务涉及到的数据一直持有写锁, 读操作结束之后就释放读锁。在事务 T1 执行的时候, 两次相同的查询得到的查询结果可能不同(没加读锁, 数据被事务 T2 修改了)发生幻读(缺乏范围锁) + 不可重复读(缺乏贯穿 T1 的读锁)
可重复读 (Repeatable Read)禁止禁止允许对事务涉及到的数据加上读锁和写锁, 未加范围锁。可能造成 T1 事务中两个完全相同的范围查询得到了不同的结果集发生幻读(缺乏范围锁)
可串行化 (Serializable)禁止禁止禁止对所有事务的读写数据全部加上读锁、写锁、范围锁能够保证绝对一致性
本文由作者按照 CC BY 4.0 进行授权