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
相关数据结构
- LockRequest 事务请求锁的数据结构。
- LockRequestQueue 当前 rid 上排队的 lock 请求队列。
- LockManager 这个主要是维护一个 <RID,RequestQueue> 的映射,如下图所示:
锁的种类
这些方法提供了为 txn 事务在给定的 tuple 上加锁的功能,也就是 S 锁和 X 锁。
实现 S 锁
- 当前 rid 上的 lock_request_queue 如果有 X 锁,等待
- 当前 rid 上的 lock_request_queue 如果有 U 锁,等待
- 以上条件不满足就可以加 S 锁
实现 X 锁
- 当前 rid 上的 lock_request_queue 如果有 X 锁,等待
- 当前 rid 上的 lock_request_queue 如果有 U 锁,等待
- 以上条件不满足就可以加 X 锁
实现 U 锁
- upgrading 置为 true
- 当前 lock_request_queue 只有一个 lock 请求,可以加 U 锁。否则等待
- 当前 lock_request_queue 只有一个 lock 请求,加 X 锁,并重置 upgrading
意向锁 (Intention Lock) 又称 I 锁,针对表锁。
当有事务给表的数据行加了共享锁或排他锁,同时会给表设置一个标识,代表已经有行锁了,其他事务要想对表加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时,逐行判断加表锁的方式效率很低。而这个标识就是意向锁。
意向共享锁,IS锁,对整个表加共享锁之前,需要先获取到意向共享锁。
意向排他锁,IX锁,对整个表加排他锁之前,需要先获取到意向排他锁。
在子树下方的所有东西上,你都会有一个与之对应的 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
杀掉一个事务并回滚事务,也可能不需要回滚整个事务,只需要回滚部分查询来释放锁,以此移除死锁,并在系统中继续推进。
杀掉哪个事务可以考虑的变量,如下图所示:
tip
MySQL 发现死锁就立即报错,所以使用的是死锁避免方法。
Postgresql 发现死锁需要等一会儿,所以进行的死锁检测。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」