目录
回忆
• 多个并发事务可能会出现问题
• 需要并发控制
- 独享共享资源,处理并发问题
在本篇内容中,我们将更正式、更详细地了解并发控制
隔离概念——I in ACID
隔离确保并发事务使数据库处于与单独执行事务相同的状态。
隔离保证一致性,前提是每个事务本身都是一致的。
我们可以通过顺序处理每个事务来实现隔离——通常效率不高并且响应时间很短。
我们需要与以下目标同时运行事务:
• 并发执行不应导致应用程序(事务)发生故障。
• 并发执行的吞吐量或响应时间不应低于串行执行。
为了实现隔离,我们需要了解操作的依赖性.
可能的依赖关系
更新丢失 | 脏读 | 不可重复读 |
T1: Read(o) T2: Write(o,1) T1: Write(o,2)
| T1: Write(o,1) T2: Read(o) T1: Write(o,2) | T1: Read(o) T2: Write(o,1) T1: Read(o) |
我们如何找到依赖关系?
给定一组事务,我们如何确定哪个事务依赖于哪个其他事务?
依赖模型
:事务 Ti 的一组输入(读取的对象)
:交易 Ti 的一组输出(被修改的对象)
注意 Oj 和 Ij 不一定不相交,即 Oj ∩ Ij ≠ empty
给定一组事务 𝜏 ,事务 Tj 不依赖于 𝜏 中的任何事务Ti,如果 -
这种方法不能提前计划,因为在许多情况下,输入和输出可能取决于状态/之前不知道。
如果并发事务的输入和输出不是不相交的,则可能会出现以下依赖关系(针对于同一对象):
1、T1 读, T2 写
2、T1 写, T2 读
3、 T1写,T2写
读-读依赖不影响隔离
当依赖图有循环时,就会违反隔离性和不一致的可能性。
依赖的正式定义
令 H 是形式为 (T, action, object) 的元组的历史序列。
设 T1 和 T2 是 H 中的事务。如果 T1 对对象 O 执行一个操作,那么 T2 对同一个 O 执行一个操作,并且在 O 上的另一个事务之间没有写操作——那么 T2 依赖于 T1。
形式上,如果存在索引 i 和 j 使得 i < j, H[i] 涉及 T1 对 O 的动作 a1,(即 H[i ] = (T1,a1,O)) 并且 H[j] 涉及 T2 对 O 的动作 a2(即,H(j) = (T2, a2,O))并且没有其他 H[k] = (T ',WRITE,O) 对于 i < k < j, 则 T2 对 T1 (T1, O, T2) 的依赖性存在于历史 H 中。
依赖图:事务是节点,如果 (Ti, O, Tj) 在 DEP(H) 中,则对象标记从节点 Ti 到 Tj 的边。
依赖关系
我们关注三种场景的依赖
• a1 = 写入 & a2 = 写入;
• a1 = 写 & a2 = 读;
• a1 = READ & a2 = WRITE(依赖于T1 可能在a2 之后再次读取)。
依赖关系 - 等价
DEP(H) = { (Ti, O, Tj) | Tj 依赖于于 Ti }。
给定两个历史 H1 和 H2 包含相同的元组,如果 DEP(H1) = DEP(H2),H1 和 H2 是等价的
这意味着通过执行 H1 或 H2 中的操作序列之一,给定的数据库将最终处于完全相同的最终状态
隔离的历史
如果一个历史相当于一个串行历史(就好像所有事务都是串行/顺序执行的),那么它被称为是隔离的
串行历史是由于一个接一个地顺序运行事务而产生的历史。 N 个事务最多可以产生 N 个! 连载历史。
如果 T1 在 T2 之前,
写成 T1 << T2。
Before(T)= {T' | T’<<T}
After(T)= {T'| T<<T'}
交易 T' 被称为虫洞事务,如果
即 T << T' << T。这意味着在历史的依赖图中存在一个循环。 虫洞事务的存在意味着它不是孤立的(=> 不是串行调度)。
一个历史是连续的,如果它一次连续运行一个事务,或者相当于一个连续的历史。
连续历史是孤立的历史。
虫洞定理:一段历史是孤立的,当且仅当它没有虫洞。
我们现在将介绍一种新型锁——
SLOCK(共享锁)允许其他事务读取,但不允许 写入/修改共享资源
虫洞事务的概念在后面的话题中会有用!
授予锁或不授予
当一个对象被另一个事务以不兼容的模式锁定时,不应将对该对象的锁定授予一个事务。
锁定模式 | |||
当前模式 | Free | 共享 | 独占 |
共享请求 (SLOCK) 用于阻止其他人写入/修改 | 兼容的 立即批准请求 将模式从免费更改为共享 | 兼容的 立即批准请求 模式保持共享 | 冲突 请求延迟,直到状态变得兼容 模式保持独占 |
独占请求 (XLOCK) 用于阻止其他人读取或写入/修改 | 兼容的 立即批准请求 将模式从免费更改为独占 | 冲突 请求延迟,直到状态变得兼容 模式保持共享 | 冲突 请求延迟,直到状态变得兼容 模式保持独占 |
隔离概念。。
事务中的操作有:READ、WRITE、XLOCK、SLOCK、UNLOCK、BEGIN、COMMIT、ROLLBACK
BEGIN、END、SLOCK、XLOCK 可以忽略,因为它们可以根据相应的操作自动插入
例如。 如果事务以 COMMIT 结束,则替换为:{UNLOCK A if SLOCK A 或 XLOCK A 出现在 T 中的任何对象 A}。 (也就是简单的释放所有的锁)
类似地,ROLLBACK 可以替换为 {WRITE(UNDO) A,如果 WRITE A 出现在 T 中的任何对象 A} { UNLOCK A 如果 SLOCK A 或 XLOCK A 出现在 T 中的任何对象 A}。
格式良好的事务:如果所有的 READ、WRITE 和 UNLOCK 操作都被适当的 LOCK 操作覆盖,那么一个事务就是格式良好的
两阶段事务:如果所有 LOCK 操作先于其所有 UNLOCK 操作,则事务是两阶段的。
概括:
事务是对以 COMMIT 或 ROLLBACK 结尾的对象的一系列 READ、WRITE、SLOCK、XLOCK 操作。
如果每个 READ、WRITE 和 UNLOCK 操作都更早地被相应的锁定操作覆盖,那么事务就是结构良好的。
如果不授予冲突授权,历史记录是合法的。
如果事务的所有锁定操作都先于其解锁操作,则该事务是两阶段的。
锁定定理:如果所有事务都是良构的(READ、WRITE 和 UNLOCK 操作较早被相应的锁定操作覆盖)并且
两阶段(锁只在最后释放),那么任何合法的(不授予冲突的授予)历史将被隔离。
锁定定理(Converse):如果一个事务没有很好地形成或不是两阶段的,那么有可能写另一个事务,使其成为虫洞。
回滚定理:执行 UNLOCK 然后执行 ROLLBACK 的更新事务不是两个阶段。
隔离程度
程度3:程度三隔离事务没有丢失更新,并且具有可重复读取。 这是“真正的”隔离。
锁定协议是两阶段的并且格式良好。
它对以下冲突很敏感:
写->写; 写->读; 读->写
程度2:程度2隔离事务没有丢失更新和脏读。
锁协议在排他锁方面是两阶段并且在读和写方面格式良好。 (可能有不可重复的读取。)
也就是说并没有保证两段锁,只保证了格式良好,如下图
它对以下冲突很敏感:
写->写; 写->读;
程度1:程度1隔离没有丢失更新。
锁协议在排他锁方面是两个阶段,在写方面是良好的。
也就是这里面只保证了两段锁,如下图
以下冲突是敏感的:
写->写;
程度 0 :如果另一个事务至少是程度1,则程度0事务不会覆盖另一个事务的脏数据。
锁定协议在写入方面格式良好。如下图
它忽略所有冲突。
ok,今天关于并发控制,隔离相关概念就说到这里了,欢迎大家随时评论区交流哦!辛苦大家了,谢谢!