CMU15445 FALL2022 Project #4 - Concurrency Control
四种隔离级别
-
未提交读(Read uncommitted)
- 这种事务隔离级别下,select语句不加锁,事务中修改的数据,即使没有提交,对其他事务也都是可见的。
- 此时,可能读取到不一致的数据,即“读脏 ”。这是并发最高,一致性最差的隔离级别。
-
提交读(Read committed)
- 一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。
- 大多数数据库系统的默认隔离级别都是提交读(但Mysql不是)。
- 提交读满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。可避免脏读,无法避免不可重复读发生。
- 因为两次执行同样的查询,可能会得到不一样的结果,这个级别有时候也叫做不可重复读(nonrepeatable read)。
-
可重复读(Repeatable read)
- 该级别保证了在同一个事务中多次读取同样记录的结果是一致的,事务中未提交的数据对其他事务是不可见的,解决 脏读 、不可重复读 的问题,是MySql默认隔离级别,但会发生幻读。
-
串行化(Serializable )
- 可串行化是最高的隔离级别。它通过强制事务串行执行,避免了脏读、不可重复读、幻读的问题。
三种问题
- 脏读:A事务正在修改数据但未提交,此时B事务去读取此条数据,B事务读取的是未提交的数据,A事务回滚。
- 不可重复读:A事务中两次查询同一数据的内容不同,B事务间在A事务两次读取之间更改了此条数据。
- 幻读:在同一事务中两次相同查询数据的条数不一致,例如第一次查询查到5条数据,第二次查到8条数据,这是因为在两次查询的间隙,另一个事务插入了3条数据。
Task #1 - Lock Manager
LockTable(Transaction, LockMode, TableOID)
-
如果
state
为COMMITED
或ABORTE
,直接返回 -
检查所请求的
LockMode
与隔离级别、state
的兼容性,ABORTED
并抛出异常- READ_UNCOMMITTED
LockMode
只能是EXCLUSIVE
或者INTENTION_EXCLUSIVE
,ABORTED
并抛出异常- 如果
state
为SHRINKING
,且LockMode
为EXCLUSIV
或者INTENTION_EXCLUSIVE
,ABORTED
并抛出异常
- READ_COMMITTED
- 如果
state
为SHRINKING
,且LockMode
为SHARED
或者INTENTION_SHARED
,ABORTED
并抛出异常
- 如果
- REPEATABLE_READ
- 如果
state
为SHRINKING
,ABORTED
并抛出异常
- 如果
- READ_UNCOMMITTED
-
在
table_lock_map_
中找到oid
对应的queue
,如果没有则创建一个,注意先给queue上锁,再释放map锁 -
循环判断
queue
中是否有id相同的,如果有-
如果
LockMode
相同,说明已经获得该锁,直接返回 -
判断当前锁请求是否为一个锁升级
upgrading
请求,不是直接ABORTED
并抛出异常 -
判断
IsUpgradeCompatible
,是否能够upgrading
,不能直接ABORTED
并抛出异常INTENTION_SHARED
可以upgrading为SHARED
、EXCLUSIVE
、INTENTION_EXCLUSIVE
、SHARED_INTENTION_EXCLUSIVE
SHARED
可以upgrading为EXCLUSIVE
、SHARED_INTENTION_EXCLUSIVE
INTENTION_EXCLUSIVE
可以upgrading为EXCLUSIVE
、SHARED_INTENTION_EXCLUSIVE
SHARED_INTENTION_EXCLUSIVE
可以upgrading为EXCLUSIVE
-
移除已有的锁请求,在
queue
中删除找到的request
,对应TableLockSet
也删除对应得 -
创建新的升级锁请求,并插入到
queue
中第一个未被授权的锁请求前(FIFO),之后加锁等待授权- 遍历
queue
,对于已授予的锁请求判断是否兼容SHARED
锁与INTENTION_EXCLUSIVE
、SHARED_INTENTION_EXCLUSIVE
和EXCLUSIVE
锁不兼容EXCLUSIVE
锁不与任何其他锁兼容INTENTION_SHARED
锁与EXCLUSIVE
锁不兼容INTENTION_EXCLUSIVE
锁与SHARED
、SHARED_INTENTION_EXCLUSIVE
和EXCLUSIVE
锁不兼容SHARED_INTENTION_EXCLUSIVE
锁只与INTENTION_SHARED
锁兼容- 如果遇到未授权的锁,且不是当前的锁返回
false
- 如果遇到
state
变为ABORTED
,从queue
删除,并唤醒等待的线程,返回
- 遍历
-
确定可以授权后,
TableLockSet
中加入request
,如果不是EXCLUSIVE
则唤醒等待的线程,返回true
-
-
queue
没有id相同请求,直接创建新请求,插入queue
,然后相同判断授权后进行插入唤醒
UnlockTable(Transction, TableOID)
- 在
table_lock_map_
中找到oid
对应的queue
,如果没有ABORTED
并抛出异常 - 检查在解锁表之前是否有行级锁被事务持有,如果有,则抛出异常,确保了在解锁表之前所有行级锁都已经被释放
- 在锁请求队列中查找当前事务已经被授予的锁请求,然后移除该锁请求并通知所有等待的线程
- 根据事务的隔离级别,更新事务的状态并返回解锁操作是否成功的标志
- REPEATABLE_READ 下如果解锁的锁模式是
SHARED
或独占锁EXCLUSIVE
,转state为SHRINKING
- READ_COMMITTED 下如果解锁的锁模式是
EXCLUSIVE
,转state为SHRINKING
- READ_UNCOMMITTED 下解锁的锁模式是
EXCLUSIVE
,转state为SHRINKING
SHRINKING
状态通常表示事务已经释放了一些资源,但尚未完成全部工作
- REPEATABLE_READ 下如果解锁的锁模式是
- TableLockSet中删除对应
request
LockRow(Transaction, LockMode, TableOID, RID)
-
开始如果判断
LockMode
为意向锁直接ABORTED
并抛出异常 -
增加判断如果
LockMode
为EXCLUSIVE
- 如果表没有该表的
EXCLUSIVE
、INTENTION_SHARED
或者SHARED_INTENTION_EXCLUSIVE
- 直接
ABORTED
并抛出异常
- 如果表没有该表的
-
后面大体与LockTable相同
UnlockRow(Transaction, TableOID, RID)
- 与UnlockTable大体相同,不需要判断是否有行级锁被事务持有
Task #2 - Deadlock Detection
AddEdge(txn_id_t t1, txn_id_t t2)
- 在txn_set_中插入t1、t2
- waits_for_的t1中插入t2
RemoveEdge(txn_id_t t1, txn_id_t t2)
- waits_for_的t1中删除t2
HasCycle(txn_id_t& txn_id)
- 使用Dfs搜寻是否存在cycle,并且返回cycle中最大的txn_id
GetEdgeList()
- 返回所有的边
RunCycleDetection()
-
循环休眠监测,首先对表和行的lock map加锁
-
循环遍历表级锁请求
- 初始化一个
granted_set
集合,用于存储已经授予的事务 ID - 如果已授权,存入
granted_set
- 否则表示该事务正在等待
- 记录其依赖关系(等待图的边),将锁请求的事务ID和锁请求的表ID存入
map_txn_oid_
,方便后续唤醒等待该表锁的其他事务 - 将当前事务 ID 和所有已授予锁的事务 ID 添加到等待图中
- 记录其依赖关系(等待图的边),将锁请求的事务ID和锁请求的表ID存入
- 初始化一个
-
循环遍历行级锁请求
- 初始化一个
granted_set
集合,用于存储已经授予的事务 ID - 如果已授权,存入
granted_set
- 否则表示该事务正在等待
- 记录其依赖关系(等待图的边),将锁请求的事务ID和锁请求的行ID存入
map_txn_oid_
,方便后续唤醒等待该表锁的其他事务 - 将当前事务 ID 和所有已授予锁的事务 ID 添加到等待图中
- 记录其依赖关系(等待图的边),将锁请求的事务ID和锁请求的行ID存入
- 初始化一个
-
循环调用
HasCycle
函数检测等待图中是否存在环路(死锁)- 如果检测到死锁,终止环路中的事务,将其状态设置为
ABORTED
- 从等待图中移除被中止的事务节点
- 通知等待该事务释放锁的其他事务
- 清理等待图和相关数据结构
- 如果检测到死锁,终止环路中的事务,将其状态设置为
Task #3 - Concurrent Query Execution
seq_scan_executor
- init时如果
IsolationLevel
不是READ_UNCOMMITTED
,要获取表的INTENTION_SHARED锁 - Next时如果
IsolationLevel
不是READ_UNCOMMITTED
,要获取行的SHARED锁 - 如果没有下一个iter且
IsolationLevel
是READ_COMMITTED
,释放所有的行锁和表锁,返回
insert_executor
- init时要获取表的INTENTION_EXCLUSIVE锁
- Next时要获取行的EXCLUSIVE锁
delete_executor
- init时要获取表的INTENTION_EXCLUSIVE锁
- Next时要获取行的EXCLUSIVE锁
最后放个线上测试图