CMU15445-2020fall 笔记:Project 4 - CONCURRENCY CONTROL

CMU15445-2020 fall 笔记:Project 4 - CONCURRENCY CONTROL

The fourth programming project is to implement a lock manager in your database system and then use it to support concurrent query execution. A lock manager is responsible for keeping track of the tuple-level locks issued to transactions and supporting shared & exclusive locks granted and released appropriately based on the isolation levels.

Lock Manager

LM(Lock Manager)用于控制事务对于数据的访问。LM 的基本思想是它维护了一个,由当前活动事务持有锁情况的内部数据结构。然后,在事务被允许访问某个数据项之前,它们会向 LM 发出锁请求。LM 将授予该事务锁,或者阻塞该事务,或者终止该事务。

需要修改的文件为 lock_manager.h / lock_manager.cpp

相关数据结构

  1. LockRequest 事务请求锁的数据结构。
  2. LockRequestQueue 当前 rid 上排队的 lock 请求队列。
  3. LockManager 这个主要是维护一个 <RID,RequestQueue> 的映射,如下图所示:

img

锁的种类

img

这些方法提供了为 txn 事务在给定的 tuple 上加锁的功能,也就是 S 锁和 X 锁。

实现 S 锁

  1. 当前 rid 上的 lock_request_queue 如果有 X 锁,等待
  2. 当前 rid 上的 lock_request_queue 如果有 U 锁,等待
  3. 以上条件不满足就可以加 S 锁

实现 X 锁

  1. 当前 rid 上的 lock_request_queue 如果有 X 锁,等待
  2. 当前 rid 上的 lock_request_queue 如果有 U 锁,等待
  3. 以上条件不满足就可以加 X 锁

实现 U 锁

  1. upgrading 置为 true
  2. 当前 lock_request_queue 只有一个 lock 请求,可以加 U 锁。否则等待
  3. 当前 lock_request_queue 只有一个 lock 请求,加 X 锁,并重置 upgrading

img

意向锁 (Intention Lock) 又称 I 锁,针对表锁。

当有事务给表的数据行加了共享锁或排他锁,同时会给表设置一个标识,代表已经有行锁了,其他事务要想对表加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时,逐行判断加表锁的方式效率很低。而这个标识就是意向锁。

意向共享锁,IS锁,对整个表加共享锁之前,需要先获取到意向共享锁。

意向排他锁,IX锁,对整个表加排他锁之前,需要先获取到意向排他锁。

img

在子树下方的所有东西上,你都会有一个与之对应的 SharedLock,在子树下方的某个地方,你也会有一个显式的 Exclusive Lock 如果你想对整个表做一次读取,你可能会去更新其中的某个值,就可以使用这个锁。

实现不同隔离级别

READ_UNCOMMITED

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

不需要使用 S 锁

READ_COMMITED

这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的 commit,所以同一 select 可能返回不同结果。

S 锁用完就释放

REPEATABLE_READ

这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。

需要 X 锁和 S 锁

2PL

2PL 是一个并发控制协议,用来决定一个事务是否能够立即访问数据库中的对象。协议将事务分为两个阶段, Growing 阶段和 Shrinking 阶段。

Growing 阶段,事务从 LM 中请求一个它所需要的锁。LM 授予或者拒绝锁请求。

Shrinking 阶段,事务只被允许释放之前获得的锁,不能获得新的锁。

2PL 主要通过控制在 tuple 的锁释放的顺序来实现不同的隔离级别。

2PL 有 cascading aborts 的问题。因为不能让 t1 事务的情况 "泄露" 到外面。当 t1 被 abort 之后,其相关的所有事务都要被 abort。

严格的 2PL,当执行完该事务,要提交该事务时,你才会去释放你的锁。在事务中一个值被写入,在这个事务结束之前,都不会被其他事务所读写。

Deadlock

死锁其实就是事务间彼此依赖成环,他们都在等待对面释放他们锁需要的锁。

deadlock detection

系统会使用一个后台线程来进行死锁检测,去查看 lock 管理器中的元数据构建一个 wait-for 图。

deadlock prevention

当一个事务想获取一个被其他事务持有的锁,dbms 杀掉其中一个来避免死锁。

老的事务会等待年轻的事务,如果 requesting transaction 具备更高的优先级,holding transaction 要比它年轻,这个 requesting transaction 就会原地等待获取这把锁。

年轻人等老人,且年轻人优先级高的话,老年人就终止,主看优先级,如果老年人优先级高,年轻人等待。

deadlock handling

杀掉一个事务并回滚事务,也可能不需要回滚整个事务,只需要回滚部分查询来释放锁,以此移除死锁,并在系统中继续推进。

杀掉哪个事务可以考虑的变量,如下图所示:

img

tip

MySQL 发现死锁就立即报错,所以使用的是死锁避免方法。

Postgresql 发现死锁需要等一会儿,所以进行的死锁检测。

Concurrent Query Execuion

Reference

posted on 2024-05-08 17:47  LambdaQ  阅读(7)  评论(0编辑  收藏  举报