Serializable Verification
我们知道 MVCC 并不能解决幻读以及写偏差的问题, 仅通过 MVCC 的事务调度是无法保证数据库引擎的 ACID 原则的, 那么为了保证数据库的 ACID 原则, 即使在调度的过程中无法保证, 可以通过在 Commit 的时候, 通过验证, Abort 可能造成写偏差于幻读的事务, 从而避免这些冲突, 完成事务执行的序列化隔离(serializable isolation level), 请注意这并不是冲突可序列化, 而是在事务提交的时候验证删选掉冲突的事务, 保证数据库从结果上来说是 ACID.
OCC (Optimistic Concurrency Control)
BUSTUB 使用的是 OCC backward validation 序列化验证方法. 由于 BUSTUB 是动态数据库, 允许增删改查, 允许各种操作, 因此需要考虑插入, 删除, 以及更新的操作. 因此对于每一个将要提交的事务, 需要存储其 Scan Fliter 与 Write Set. 当前有了所有信息(Read Set, Write Set, 版本链等), 我们可以通过检查扫描谓词(Read Set)是否与当前事务开始后其他已提交事务的 Write Set 存在交集, 从而进行可序列化的验证. 每次事务 Txn_A 将要提交的时候, 需要进行以下的验证过程:
- 如果 Txn_A 是只读事务, 无需验证, 在 BUSTUB 中仅验证, UPDATE, DELETE, 和 INSERT, 因为只读事务不会对版本链造成修改, 从 OCC 的角度来说, 不会影响事务的隔离性.
- 遍历在 Txn_A 的 read timestamp 时间戳后 Commit 的所有事务, 这些事务的集合为 "conflict transactions", 表示可能存在冲突的事务
- 收集 "conflict transactions" 修改的 tuples 的 RIDS.
- 对于步骤 3 中的 RIDS 集合, 遍历每一个对应的 tuple 的版本链, 判断与当前将要提交的事务 Txn_A 之间是否存在幻读. 这个过程需要从当前时候想 Txn_A 的 read timestamp 开始遍历版本链, 回放其他事务的更新流程, 检查是否存在冲突.
我们详细解释一下上述的步骤 4 可能存在冲突, 从而 Abort 事务 Txn_A 的情况:
我们遍历 Write Set 的 tuples 中的每一个 tuple 的版本链
- 如果一个 tuple 是新插入的, 需要判断这个新插入的 tuple 是否满足当前事务的 Scan Fliter, 我们可以用下图来解释这种情况.
可以看到, 图中的 Scan Fliter 是col2=8
, Txn1 的 read_ts 为 2, 它开始的时候, 是不会读取到这个 tuple 的, 但是 Txn2 插入了 (B,8), 并且成功 Commit 了, 按照事务的序列化执行标准, 事务 Txn1 Commit 的时候应该读取这个 (B,8) 并且修改为 (A,4), 但是按照 MVCC 版本链的控制, 是不会读到新插入的 Tuple 的, 因此当前事务 Txn1 Abort.
需要 Abort 的情况
scan predicate(read set): 当前将要 Commit 的事务在执行过程中读过的所有 tuples 对应的 RIDs 集合.
conflict transactions: 当前将要 Commit 的事务的 read_ts_ 之后 Commit 的所有事务的集合