【CMU 15-445】Proj4 Concurrency Control

CMU-15445汇总
本文对应的project版本为CMU-Fall-2023的project4
由于Andy要求,本博客只提供思路,不会公开任何代码
终于要完结15445的所有project了(一些leaderboard没写,以后再说吧,至少基础的部分都完成啦)
拖了好久,期间有实习毕设等各种各样的事情阻碍了进程

通关记录

本project用时约6天
在这里插入图片描述

Task1 Timestamps

Task1比较简单,就是为事务维护读取时间戳与提交时间戳,并维护watermark值(系统中所有活跃事务的最小读取时间戳,后续用于垃圾回收)。
transaction_manager.cpp中,设置事务的读取时间戳与提交时间戳,当前事务的读取时间戳为系统中最新提交事务的提交时间戳(last_commit_ts_);当前事务的提交时间戳是系统中最新提交事务的提交时间戳+1(last_commit_ts_ + 1)。
watermark值用map简单维护一下即可(current_reads_)。

Task2 Storage Format and Sequential Scan

Task2也相对简单,实现版本的重构与sequential scan逻辑的重写(需要根据事务的读取时间戳查找可见版本)。
由于在bustub中,版本链的维护采用undo log的形式(每个undo log只存放partial tuple),最新记录存放在table heap中,需要沿着版本链逐步复原可见版本,故Task2需要实现一个版本重构函数ReconstructTuple
该函数接收一个undo log数组和base_tuple,需要以base_tuple为初始记录,遍历该undo log数组,恢复出最终的版本,逻辑并不复杂。对undo log的每一次迭代大致分为三部分:

  • 遍历undo log的modified_fields字段,提取出需要修改的列
  • 根据提取出的需要修改的列,利用Schema::CopySchema函数,提取出对应的partial schema
  • 利用partial schema,提取出partial tuple各个字段的值,并进行修改

实现完ReconstructTuple函数后,则可以重写seq_scan_executor.cpp中的记录扫描逻辑。原始的扫描逻辑只会查看table heap上的所有记录,而在新增了MVCC的版本链之后,可能需要从表堆开始,沿着版本链查找可见版本。
根据当前事务的read_ts与表堆中tuple的时间戳ts的大小关系,分为以下两种情况:

  • ts <= read_tsts == txn_id:表堆中的tuple对当前事务可见,直接返回
  • else:表堆中的tuple对当前事务不可见,需要沿着版本链查找可见版本

undo log之间通过undo link进行连接(undo link存放下一个undo log的位置,undo log存放着下一个undo link),在版本链上查找可见版本时,获取第一个undo link需要通过GetUndoLink函数,后续的undo link则存放于undo log中。

Task3 MVCC Executors

Task3任务量会大一些,但拆分后也不难。

Task3.1 Insert Executor

首先重写insert executor,由于insert操作不会影响版本链(因为是新纪录,版本链是空的),故只是添加上设置tuple时间戳的代码,需要设置时间戳为当前事务id。同时,需要将该tuple的rid添加到当前事务的write set中,方便commit时修改tuple时间戳。

Task3.2 Commit

当一个事务commit时,我们需要将该事务write set中的所有记录的时间戳设置为该事务的提交时间戳。

Task3.3 Update and Delete Executor

需要重写update executor和delete executor,它们的逻辑非常类似,因此需要将一些代码打包成函数放在execution_common.cpp中,方便重用。
以更新逻辑为例,主要分为以下步骤:

  • 检查是否产生Write-Write Conflict,若产生则终止事务,否则继续往下
  • 判断当前事务是否第一次更新该记录(查看表堆记录的时间戳与当前事务id是否一致)
    • 若为第一次更新,则需要创建新的undo log,并更新table heap中的记录以及undo link
    • 若不是第一次更新,则只需要修改之前创建过的undo log,更新table heap中的记录(确保事务在每条tuple上只存在一个undo log)

Task3.4 Stop-the-world Garbage Collection

由于bustub中的undo log管理在事务中,所以垃圾回收也以事务为单位,主要步骤如下:

  • 遍历txn_map_的所有事务
    • 遍历当前事务的write set,从table heap中遍历write set中的每个记录,如果该事务创建的所有undo log均无效,则标记
  • 回收被标记且已提交的事务

Task4 Primary Key Index

Task4会考虑主键依赖,并开始出现多线程的测试,debug会痛苦一些

Task4.0 Index Scan

与Task2顺序扫描类似,加上版本链查找逻辑即可

Task4.1 Inserts

插入逻辑需要考虑主键依赖,在插入之前,首先检测所插入的记录是否包含主键索引,如果所包含的主键索引在数据库中已存在,那么分为以下两种情况(如果只完成Task4.1及之前的所有任务,则不需要考虑这两种情况,均终止事务即可)。

  • 若主键索引指向的记录已被删除,走更新流程
  • 若主键索引指向的记录未被删除,终止当前事务
    若主键索引不存在,则插入记录,并更新索引。若更新索引时更新失败(因为可能有多个线程并行运行),那么也要终止当前事务。

Task4.2 Index Scan, Deletes and Updates

删除与更新逻辑均涉及版本链的更新,在多线程环境下,需要保证删除与更新逻辑的线程安全性。这里采用VersionLInk类中的in_progress字段实现无锁安全性,具体方法是:

  1. 检查写写冲突
  2. 循环获取in_progress字段,直到它的值为false或者循环超时(我设置的超时次数为100次)
  3. 备份version_link
  4. 更新in_progress字段为true(使用UpdateVersionLink函数,参数中的check函数需要检查之前保存的version_link是否被修改过)
  5. in_progress字段更新成功,则进行记录的删除或更新逻辑;若更新失败,则回到第一步重新进行

记录的更新与删除逻辑与Task3.3一致,不再赘述

Task4.3 Primary Key Updates

当Update算子涉及到对主键的更新时,使用以前的更新逻辑会导致主键索引所指向的记录出错(因为主键的值被更新了,会映射到新的索引上)。
因此,这里的处理方法是,将记录删除后添加,不修改索引。

Bonus1 Abort

在事务终止时,需要回退事务的修改,遍历事务的writeset,根据undo log修改表堆记录即可。这里有两种实现方法,第一种是直接修改表堆记录,不改变version link,这会导致undo log的冗余;第二种是修改完表堆记录后,修改version link,跳过第一个undo log(因为它已经没用了)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值