CMU15445 Concurrency Control

qps

  • Project #1 - Buffer Pool Manager, qps, 2323.73636
  • Project #2 Checkpoint #2: B+Tree, qps, 151510.33
  • Project #3 - Query Execution, time, 4812.76133s
  • Project #4 - Concurrency Control, update qps, 10.6529, read qps 10.3618, weighted qps 10.5946

Lock Manager

image

lock

  1. 检查事务的隔离级别是否符合锁的要求
REPEATABLE_READ:
   The transaction is required to take all locks.
   All locks are allowed in the GROWING state
   No locks are allowed in the SHRINKING state

READ_COMMITTED:
   The transaction is required to take all locks.
   All locks are allowed in the GROWING state
   Only IS, S locks are allowed in the SHRINKING state

READ_UNCOMMITTED:
   The transaction is required to take only IX, X locks.
   X, IX locks are allowed in the GROWING state.
   S, IS, SIX locks are never allowed
  1. 是否是重复的锁请求

  2. 是否为锁升级

  3. 将锁请求加入队列

  4. 尝试获取锁,看这个请求与已经分配资源(granted)的请求(如有其他线程在读被分配了读锁)是否互斥或者说兼容;如果满足的话,其次看优先级,满足 fifo;

  1. 判断兼容性。遍历请求队列,查看当前锁请求是否与所有的已经 granted 的请求兼容。需要注意的是,在我的实现中 granted 请求不一定都在队列头部,因此需要完全遍历整条队列。锁兼容矩阵可以在 Lecture slides 中查看。若全部兼容,则通过检查。否则直接返回 false。当前请求无法被满足。
  2. 判断优先级。锁请求会以严格的 FIFO 顺序依次满足。只有当前请求为请求队列中优先级最高的请求时,才允许授予锁。优先级可以这样判断:
    如果队列中存在锁升级请求,若锁升级请求正为当前请求,则优先级最高。否则代表其他事务正在尝试锁升级,优先级高于当前请求。
  3. 若队列中不存在锁升级请求,则遍历队列。如果,当前请求是第一个 waiting 状态的请求,则代表优先级最高。如果当前请求前面还存在其他 waiting 请求,则要判断当前请求是否前面的 waiting 请求兼容。若兼容,则仍可以视为优先级最高。若存在不兼容的请求,则优先级不为最高

这里有个问题,尝试获取锁的时候,确定了当前 request 不是升级锁的请求,且判断了之前的 waiting 的 request 都与当前的兼容,那么可以获取锁(具有相同的最高优先级),那么之前 waiting 的 request 呢?

这里这段话来自 shiyi

unlock

  1. 对于 table lock,释放时检查所有的 rowlock 是否已经释放
  2. 获取对应的 lock request queue
  3. 遍历请求队列,找到 unlock 对应的 granted 请求

当隔离级别为 REPEATABLE_READ 时,S/X 锁释放会使事务进入 Shrinking 状态。当为 READ_COMMITTED 时,只有 X 锁释放使事务进入 Shrinking 状态。当为 READ_UNCOMMITTED 时,X 锁释放使事务 Shrinking,S 锁不会出现。

deadlock dection

实验说明中提到,要使用 MakeEagerIterator 而不是 MakeIterator 获取迭代器。这貌似是一个 Halloween Problem?

死锁的检测与解除,这个就是检测资源是否冲突。

  1. 遍历不同的 request queue,建立有向图,对于同一个 request 里面,应该构建任意 waiting 到 任意 granted 事务的边;应该只有一个有向图,但可能不是单连通,因为可能一个事务访问多个表,有可能只访问一个表,表就可以看出资源(或者表上的锁)
  2. 搜索是否有环,如果有环,找到 txn_id 最大的事务(最年轻的事务),将此事务的 state 设置为 abort
  3. 找到事务正在请求资源的 request queue,notify_all 将 request_queue 上所有线程唤醒。
  4. 如果事务唤醒后发现状态为 abort (这里需要修改一下 上文的等待条件) ,在 request_queue 中将自己删除,抛异常,返回。(对应于 locktable 和 lockrow 中 while(!CanLockUpgrade))此时相当于环断了
  5. 在 graph 中删除 所有与 txn_id 相关的边,可能会有多条边,因为会同时在等待多个事务。
  6. 重复这个过程,因为可能有多个环。

Concurrent Query Execution

实际上就是并发的实现之前的 excutor 算子

seq scan

  1. 对于写,lockmode 是 IX,看是否能用 IX 锁住表
  2. 对于读,如果是 READ_UNCOMMITED,就不需要任何锁,否则需要 S 锁,但这里不能用 S,否则后面的 MixedTest 会有问题,所以需要 IS,如果当前事务已经申请了 IX 锁,那么就没有必要 IS 锁了。
  3. 每读取一行需要 row lock(ru 不需要 S,rc 和 rr 都需要),即使这一行是被删除的,如果是有效的行就 row unlock(ru 级别不需要加读锁,那么读锁也就不需要 unlock,rc 级别都需要,rr 释放锁在 SHRIKING), 无效就 unlock 看下一行。如果发现到了表的结尾,那么需要 unlock table(如果是 delete 操作,seqcan 并没有真正完成 delete 操作,因此不能 unlock table,只有当是读操作时,才可以 unlock table)

insert && delete

aborted 时会 undo 之前所有的操作,因此每个 transaction 维护了一个 table_write_set_ 和一个 index_write_set_,插入删除时需要记录一下。

并且,插入和删除是写操作,需要申请 row lock,不需要手动释放,因为这似乎是实验需要满足的 2pl。

** insert 获取锁**

  1. 获取 IX 表锁
  2. 获取 X 行锁

事务在被 abort 后,lock_manger 会自动释放相关资源,所以我们不需要添加冗余的逻辑去释放已有的锁。
https://zhuanlan.zhihu.com/p/600001968

ref

  1. https://zhuanlan.zhihu.com/p/592700870
posted @   o0yo  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示