众所周知,数据在写入时往往先被写入内存缓冲区,之后在某一时间才会真正持久化到磁盘中。对于简单的增、删、改、查操作,可以等待数据写到磁盘后再返回成功,这样就不会出现一致性问题。但在实际应用中,经常会出现一组操作需要作为整体执行的情况,多条语句要么都执行成功要么都执行失败,不允许出现数据不一致的情况。基于这样的需求,数据库提供了事务机制,用于将一系列数据库操作组合成一个完整的逻辑过程。
9月22日 19:30 从0到1内核实战教程第六期将带你学习数据库事务引擎相关内容,从基础的事务模型和日志记录介绍开始, 结合 OceanBase 事务处理与分布式事务的实现讲解,从底层实现原理的角度,帮助你深入了解数据库事务机制是如何设计与实现的。
本期能帮你解决什么问题?
1、undo log 和 redo log,是如何处理数据不一致问题的?
2、相对于传统的两阶段提交协议,OceanBase 事务都做了哪些优化?
3、并发控制是如何解决读写不相互阻塞问题的?
直播内容抢“鲜”知
故障类型和事务模型
故障类型
- 数据输入错误
数据内容错误是无法避免的,如果用户在输入姓名和身份证号时输错了一位,错误很难被直接发现。处理数据输入错误的常见技术就是编写约束和触发器,及时找出不满足格式的错误数据。
- 介质故障
磁盘的部分区域损坏,一般只会影响少数几位数据,可以通过奇偶校验等方式检测到。如果整个磁盘损坏,要么为数据维护一个备份,要么采用 RAID 阵列保存数据。此外还可以将数据的副本保存在多个远程节点上,一个节点的故障不会导致数据丢失,这就是分布式的思想。
- 系统故障
系统故障主要包括掉电和软件错误,由于内存是“易失性的”,如果数据提交了但没有刷新到磁盘,当系统掉电时这部分数据就会丢失。软件错误可能直接覆盖内存中的数据,相当于数据已经丢失。解决这类问题的办法就是维护日志,将所有对数据库的修改操作都记录下来,以便重启系统后进行恢复。
- 机房故障
机房故障有可能是整个机房发生了火灾、爆炸等意外,也可能是自然灾害导致一片地区受到破坏,这种情况下 RAID 和数据校验都无法发挥作用,只有(异地)备份机制可以防止数据丢失。
事务模型
为了更好地理解事务机制,我们建立一个模型(见图 6-1 )。查询处理器负责解析 SQL 命令,事务管理器统筹管理事务的执行,日志管理器维护日志,缓冲区管理器负责维护内存缓冲区和刷写数据,此外还有恢复管理器负责在系统重启后恢复数据。
图 6-1 事务管理器与日志管理器
事务原语
在数据库运行过程中,刚插入的数据往往不会直接写入磁盘,而是先缓存在内存中。对于一个运行中的数据库,我们将其地址空间简单分成三个部分:
- 持久化保存数据的磁盘空间。
- 缓冲区对应的内存或虚拟内存空间。
- 事务的局部地址空间(也在内存中)。
事务要读取数据,首先要将数据读取到缓冲区中,然后缓冲区的数据可以被事务读取到局部空间。事务的写入过程与此相反,先在局部空间中创建新值,然后再将新数据拷贝到缓冲区中。缓冲区中的数据不会立刻持久化到磁盘,通常是由缓冲区管理器决定何时写入磁盘。
为了便于研究日志和事务管理的细节,我们使用一系列原语来描述数据库操作:
- INPUT(X):将数据库元素 X 从磁盘拷贝到缓冲区。
- READ(X, t):将数据库元素 X 从缓冲区拷贝到事务的局部变量 t。
- WRITE(X, t):将局部变量 t 的值拷贝到缓冲区的数据库元素 X,如果 X 不在缓冲区,先执行 INPUT(X)。
- OUTPUT(X):将数据库元素X从缓冲区拷贝到磁盘。
在这里我们假设:数据库元素X的大小 = 磁盘块大小 = 缓冲区块大小。
以银行转账事务为例,一个数据库中有 A、B 两个账户(元素),A 和 B 之间进行转账操作,在任何一致的状态中它们的值的总和是固定的。一个转账事务 T 主要有两个步骤:A := A-10;B := B+10。
假设 A 和 B 的初值都为 15,事务T从一个一致的状态(A+B=15+15=30)开始,事务正常执行且期间没有发生系统故障,那么最终的状态必然也是一致的,A 和 B 的值发生了变化,但他们的和没有变(A+B=5+25=30)。
T的执行包括从磁盘读取 A 和 B,执行运算,将 A 和 B 的新值写入缓冲区。之后缓冲区管理器会执行 OUTPUT 原语,将数据写回磁盘。表 6-1 中展示了事务 T 的执行过程,以及每个步骤执行之后 A 和 B 在缓冲区和磁盘中的值。
表 6-1 事务 T 的执行过程
第 1 步 READ(A, t) 命令将 A 的值拷贝到局部变量 t 中,如果 A 不在缓冲区中,那么就会先执行 INPUT(A) 命令;第 2 步将 t 减 10,这一步不会改变A在缓冲区和磁盘上的值;第 3 步将 t 写到缓冲区的 A 中,这一步也不会影响磁盘上 A 的值。直到第 7 步,OUTPUT(A) 将 A 的新值写入磁盘,完成持久化。
如果表 6-1 中的步骤顺利执行,事务前后数据库都处于一致性状态。如果在 OUTPUT(A) 之前系统发生故障,因为磁盘上的数据没有任何变化,一致性得以保持。如果系统故障在 OUTPUT(B) 之后发生,磁盘上的 A 和 B 都已经修改,仍然满足一致性要求。但如果系统故障发生在 OUTPUT(A) 和 OUTPUT(B) 之间,那么数据库就会处于不一致的状态,这种情况下就需要进行修复,要么将 A 和 B 都重置为原值,要么将它们都更新为新值。
undo/redo 日志
日志就是记录事务相关操作的文件,每个改变数据库的操作都会生成日志记录,在系统故障发生之后,我们可以通过日志将数据库恢复到一致性状态。
undo 日志记录
回滚日志(undo log),它通过撤销未提交的事务来恢复数据库的一致性状态。
日志由多个日志记录组成,每条日志记录对应一个重要操作,比如更新数据、删除数据、提交事务等,这些操作往往会改变数据库的一致性状态。日志空间最初在内存中由缓冲区管理器分配,日志记录则由日志管理器负责创建,缓存中的日志记录会被尽快写到持久性存储介质中。记录事务操作的几种日志记录类型如下。
- [START T]:表示事务T开始。
- [COMMIT T]:表示事务T已经完成。[COMMIT] 记录不能保证数据已经更新到磁盘上,如果需要的话应由日志管理器控制事务的逻辑。
- [ABORT T]:表示事务T无法完成,需要终止。如果事务终止,那么它所做的任何更改都不能写到磁盘上,如果已经写到磁盘就必须消除这些影响。
- [T, X, v]:undo 日志的一条更新记录,表示事务T改变了元素 X,它的原值是 v。更新记录在数据库对内存执行 WRITE 操作时产生,不需要记录数据库元素的新值。
事实上日志记录的类型还有很多,涉及的操作也不限于插入、更新、删除等基础操作,为了便于说明,在本节中我们只考虑对已有数据的更新操作。
redo 日志
undo 日志有一个限制,即我们在将事务对数据的修改刷写到磁盘前不能提交该事务。这样做可能会导致事务因为等待磁盘 IO 而被阻塞,也会占用较多的磁盘带宽。此外,由于每次故障恢复都要把相关数据恢复到事务之前的状态,即便这些数据已经部分或全部更新到磁盘了,这样反复修改岂不是浪费了很多资源。
事实上只要有日志用于恢复数据,那么把修改的数据暂存在主存中是安全的,并非一定要马上同步到磁盘。redo(重做)日志就是这样一种日志,它不需要等待数据写到磁盘就可以提交事务,它与 undo 日志的区别是:
- undo 日志在恢复时撤销未完成事务的操作并忽略已提交事务,而 redo 日志忽略未完成事务并重做已提交的事务。
- undo 日志要求先将修改后的数据写到磁盘再写入 COMMIT 日志记录,而redo日志要求我们先写入 COMMIT 日志记录再将修改后的数据写到磁盘。
- 在使用 undo 日志时,我们记录的是数据库元素的旧值,在使用 redo 日志时,我们记录的是数据库元素的新值。
更多详细内容,敬请关注 9月22日 19:30 「从 0 到 1 数据库内核实战教程」官方课程。
附录:
内核实战教程第一期 | 成为内核开发者的第一步:搭建研发环境
内核实战教程第二期|带你揭开数据库存储结构的神秘面纱
内核实战教程第三期|为什么索引可以让查询变快?
内核实战教程第四期|带你走进数据库 SQL 引擎
内核实战教程第五期 | SQL 执行引擎的设计与实现
课程回放
赶快扫描下方二维码进入「OceanBase 入门到实战」群
关注课程动态,和更多小伙伴一起学习进步
为帮助大家更好地学习数据库知识,结交新的朋友
未来 OceanBase Meetup 也会走到更多的城市中
大家进群后修改自己的群昵称哦【格式:姓名-城市-职位】