Lab 3 多版本并发控制
记录可见性的判断方法
见 src/table/table_scan.cpp - IsVisible()
中的具体实现.
可重复读隔离级别:
- 记录被删除:
- 默认可见;
- 如果对应的事务在当前事务之后, 则记录可见;
- 如果对应的事务不是活跃事务, 则记录不可见.
- 记录未被删除:
- 默认可见;
- 如果对应的事务是当前事务, 则记录不可见;
- 如果对应的事务在当前事务之后, 则记录不可见;
- 如果对应的事务是活跃事务, 则记录不可见.
读已提交/串行化隔离级别:
- 记录被删除:
- 默认可见;
- 如果对应的事务是当前事务, 则记录不可见;
- 如果对应的事务不是活跃事务, 则记录不可见.
- 记录未被删除:
- 默认可见;
- 如果对应的事务是当前事务, 则记录不可见;
- 如果对应的事务是活跃事务但不是当前事务, 则记录不可见.
基础功能
解决万圣节问题
添加记录头信息见 src/table/table_page.cpp
.
- 通过
record->SetCid()
,record->SetXmin()
,record->SetXmax()
设置相应的记录头信息.
修改可见性判断条件见 src/table/table_scan.cpp
.
- 满足
record->GetXmax() == NULL_XID
且record->GetXmin() != xid || record->GetCid() != cid
的记录是可见的, 即记录未被删除且不由当前命令设置.
实现可重复读隔离 / 实现读已提交隔离
设置活跃事务集合见 src/executors/seqscan_executor.cpp
.
- 分别通过
GetSnapshot()
和GetActiveTransactions()
获取事务开始时的活跃事务集合以及即时的活跃事务集合.
修改可见性判断条件见 src/table/table_scan.cpp
.
- 分已删除记录和未删除记录进行处理, 见报告第一节.
强两阶段锁
实现锁管理器 LockManager 见 src/transaction/lock_manager.cpp
.
使用
std::vector
保存表锁和数据行锁信息.struct TableLock { LockType lock_type; xid_t xid; oid_t oid; }; struct RowLock { LockType lock_type; xid_t xid; oid_t oid; Rid rid; }; class LockManager { ... private: std::vector<TableLock> table_locks_; std::vector<RowLock> row_locks_; }
LockTable()
与LockRow()
先检查是否存在其他事务加的锁, 以及锁的类型相容性, 再检查本事务是否持有锁, 并升级锁的类型, 最后成功获取锁.Compatible()
实现参考 PPT 给出的意向锁相容性矩阵.Upgrade()
实现如下, 需要注意的是特殊判断 SIX 锁的获取.LockType LockManager::Upgrade(LockType self, LockType other) const { if (self == other) { return self; } if (self == LockType::S && other == LockType::IX) { return LockType::SIX; } return other; }
为修改操作加锁见 src/executors/*
.
- 普通
select
语句对表加 IS 锁; 数据修改操作对表加 IX 锁, 对修改的数据行加 X 锁.
实现可串行化隔离
实现 select for update/share 加锁见 src/executors/lock_rows_executor.cpp
.
- 根据
plan_
的lock type
, 对表加 IS/ IX 锁, 对相应数据行加 S/X 锁.
设置活跃事务集合见 src/executors/seqscan_executor.cpp
.
- 完全与读已提交隔离相同.
修改可见性判断条件见 src/table/table_scan.cpp
.
- 完全与读已提交隔离相同.
实验耗时
- 基础功能
- 解决万圣节问题: 1h.
- 通过多版本并发控制实现可重复读隔离: 0.5h.
- 实现读已提交隔离: 1h.
- 强两阶段锁: 1h.
- 通过多版本两阶段锁实现可串行化隔离: 0.5h.