(十6、十七)数据库并发控制(上)

(十6、十七)数据库并发控制(上)

1. 简介和引入知识

1. 事物

1. 事物的概念

​ 事物这个概念在数据库中可谓是最为常见。它是指一些列操做序列(一个或一个以上)当一个事务被提交给了DBMS(数据库管理系统),则DBMS须要确保该事务中的全部操做都成功完成且其结果被永久保存在数据库中,若是事务中有的操做没有成功完成,则事务中的全部操做都须要被回滚,回到事务执行前的状态(要么全执行,要么全都不执行);同时,该事务对数据库或者其余事务的执行无影响,全部的事务都好像在独立的运行。从事物的概念出发。就能够引出事物的四大特性。算法

一些形式化的定义为了方便后面的描述数据库

A,B,C来表示数据对象并发

R(A),W(B) 来表示对于数据对象的读写操做ui

2. 事物的四大特性

这里感受cmu ppt里面对于四种特性一句话的归纳特别好。spa

  1. 原子性(Atomicity): all or nothing
  2. 一致性(Consistency): it looks correct to me
  3. 隔离性(Isolation): as if alone
  4. 持久性(Durability): survive failures

3. 确保原子性的机制

首先考虑这样一个问题。加入我把当前帐户的$100取出转帐给andy。可是在咱们取出它以后,转帐给andy以前这个事物忽然终止了。或者停电了。这样若是dbms什么都不作,就有100 ¥ 蒸发掉。那么如何解决这一问题。3d

1. Logging(日志)日志

dmbs的日志会记录全部的行为。这样就能够当事物被abort的时候撤销这个事物已经执行了的无效行为。几乎全部的DBMS都是用了这种方法code

2. Shadow Paging对象

在这种机制下DBMS复制全部的page。当事物对这些page进行改变的时候。会改变这些page的副本。只有当这个事物成功commit以后。这些被改变的副本就会对其余用户可见。blog

2. 两种常见的并发协议和例子

并发协议指的是。dbms如何控制多个事物的交错执行。

两种最多见的协议分别是

  1. 悲观协议: 从一开始就不要让问题出现。
  2. 乐观协议: 假设冲突很是少。只有当发生的时候才会解决它

下面来看一些并发交错执行的例子

1. 顺序执行example

Assume at first A and B each have $1000.

image-20210403140934980

左边T1先执行T2后执行整个执行过程大概以下

T1:

A = A - 100 = 900

B = B + 100 = 1100

T2 :

A = A * 1.06 = 954

B = B * 1.06 = 1166

右边则T2先执行。T1后执行。执行的结果和左边是彻底一致的

2. INTERLEAVING EXAMPLE (GOOD)

image-20210403144002482

能够发现对于左边。虽然在两个事物之间有交叉。可是最后的结果是同样的。咱们说左边这个调度是可序列化的。

3. INTERLEAVING EXAMPLE (BAD)

image-20210403144220981

对于上面的操做。他最后的结果和序列化的结果不同。则这个调度就是不正确的

3. 几种冲突

发生冲突的操做主要分为下面三大类

  • Read-Write Conflicts (R-W)
  • Write-Read Conflicts (W-R)
  • Write-Write Conflicts (W-W)

1. READ-WRITE CONFLICTS

读写冲突形成的问题就是不可重复读的问题

image-20210403144825104

事物T1先开始执行。首先读出来A的值发现位10。接下来执行权交给了事物T2运行。T2对A进行了写操做。可是对于事物T1而言,它会认为它没有对A进行了修改操做。所以当事物T2提交以后,执行权回到T1的时候,T1再读取A发现读出来的为19。这里就存在这错误。

2. WRITE-READ CONFLICTS

读未提交的数据(脏读)

image-20210403145604732

这里发生的问题就是。事物T1先修改了A的值,可是后面这个事物被abort了。可是T2觉得这个值已经被修改了。因此它直接读了T1修改完以后读值进行了操做。

3. WRITE-WRITE CONFLICTS

image-20210403150541426

这里对于T1而言它不知道A被从新写了两次。

2. CONFLICT SERIALIZABILITY INTUITION

首先经过两个例子看一下有冲突的能够序列化的调度。和有冲突不能序列化的调度

冲突的几种状况在上面已经介绍过了

image-20210406202159285

能够发现左边和右边并不等价。

那如何判断一个调度是不是冲突可序列化的那。最经常使用的算法就是依赖图算法(lab4中也有用到这个)

点:事物来表示点

边:若是事物Ti的一个操做Oi与事物Tj的一个操做Oj发生冲突。而且操做Oi发生在Oj以前。那么存在一条从事物Ti指向事物Tj的边

若是出现环。则没法冲突可序列化

image-20210406202500332

1. 依赖图算法举例

1.1 例子一。😈

image-20210406202649448

image-20210406202925455

1.2 例子2 🌟

  1. 首先有三个事物。先在图中画出三个点
  2. 找到不一样事物之间的冲突关系。而后获得边

image-20210406212728551

image-20210406212735697

image-20210406212802658

2. VIEW SERIALIZABILITY算法

除了依赖图以外。还有一个算法来判断是否能够冲突序列化

这个算法让咱们站到一个更高的层次来判断是否能够序列化。若是两个调度S和S‘知足下面的条件。则咱们说这两个调度等价

image-20210407135626240

image-20210406213636555

对于上面这个例子。若是是基于依赖图算法的话。那么它就是一个没法冲突可序列化的调度。可是根据VIEW SERIALIZABILITY算法。它能够转换成一个可序列化的算法

对于这两个调度。咱们站到更高的层次上来看。对于读操做。都是读到最刚开始A的值。对于写操做。写的顺序虽然交换了。可是并不影响结果。

3. 两段锁协议

最经典。也是实验要用到的并发协议。

首先为何叫两段锁协议那。确定是由于它有两个阶段吧(:。

阶段1:Growing

这一阶段每一个事物须要从DBMS的锁管理处得到锁

阶段2:Shrinking

这一阶段事物只容许释放以前得到的锁。而不容许加上新的锁

image-20210407103005082

在看一个例子以前。先来看一下这个协议中的锁的类型。

两大类的锁分别为排他锁和共享锁

image-20210407103112502

3.1二阶段锁协议的例子

image-20210407103258868

从这个例子来看二阶段锁彷佛能够完美的运行。事物T1先开始执行。因为这一事物的操做中有写操做。因此事物T1对A加上排他锁。这样当事物T2开始的时候。因为事物T2也想写A。因此事物T2也去尝试加上排他锁。可是因为A此时有一个排他锁。(事物T1持有的。此时尚未释放)因此事物T2这个时候就须要wait。等到事物T释放对于A的锁以后。事物T2才能得到对于A的加锁权。随后事物T2对A执行写操做。

可是二阶段锁有他的缺点

image-20210407103744876

好比对于上面这种状况。事物T1先修改了A。可是这个事物后面ABORT掉了。可是T2并不知道,因此T2这里仍是会读出A的值。而后在进行修改。这样就可能产生连锁错误。也就是上面提到的。脏读。因此为了解决这种问题。DBMS会由于T1的abort也把T2 abort掉。

3.2 强限制的2PL

看下面这个例子。有两个事物。分别以下

image-20210407104541329

先看没有2PL的状况

image-20210407104641694

总感受这里应该是1900。不过确定有问题就是了。下面看引入2PL。如何解决这一问题

image-20210407105000744

这里符合2PL。加锁和解锁两个阶段彻底分开。并且对于有写操做的加X锁。对于只须要读操做的加S锁

严格的2PL协议是说若是这个事物。若是一个value要被一个事物写的话。那么在这个事物完成以前。其余的事物都不容许读或者重写它

image-20210407105604763

这样就能够避免。脏读的出现。

3.4 2PL可能会致使死锁

死锁现象是因为两个事物循环等待锁。致使的

image-20210407105956821

如何探测死锁在上面的依赖图算法有说过

1. 死锁处理 ---> 牺牲者 + 回滚

根据必定的原则选择一个牺牲者。(能够是年龄最小、最大、或者是最近最少使用等等原则)。而后决定回滚多少关于这个事物的改变。

2. 死锁预防

2.1. 当一个事物尝试去得到一个由其余事物持有的锁时。DBMS会选择一个kill掉来防止死锁。

有两种不一样的策略来实现这一机制

这是一种基于抢占的机制

这里的优先级就是它开始的时间戳。

[Attention]: 这里的事物若是回滚了。它会以它以前的时间戳重启

image-20210407111104746

用下面这个例子来解释一下上面的策略。

  1. T1 比T2有更高的优先级。T1这里须要等待T2对于A的锁释放。若是按照wait-Die而言。优先级高的要等待优先级低的释放。因此T1就会等待。可是若是按照wait-wait而言t2就会被aborts掉。

image-20210407111410895

2.2 Lock timeouts

这种机制下。一个事物等待锁的时间是有限制的。若是超时的话它就会回滚和重启。

这种机制很是容易实现。可是问题就是如何界定这个超时时间

3.5 意向锁

1. 为何须要意向锁

下面来看一下意向锁的做用:参考自https://www.zhihu.com/question/51513268/answer/834445344
事务A锁住了表中的一行,让这一行只能读(数据库自动给该表增长意向共享锁),不能写。以后,事务B申请整个表的写锁。那么事物B会怎么作那。咱们来看没有意向锁的

若是没有意向锁则是这样的
step1:判断表是否已被其余事务用表锁锁表
step2:判断表中的每一行是否已被行锁锁住。

若是没有意向锁的话,则须要遍历全部整个表判断是否有行锁的存在,以避免发生冲突

那么若是增长了意向锁以后那

step1:判断表是否已被其余事务用表锁锁表
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,所以,事务B申请表的写锁会被阻塞。

若是有了意向锁,只须要判断该意向锁与即将添加的表级锁是否兼容便可。由于意向锁的存在表明了,有行级锁的存在或者即将有行级锁的存在。于是无需遍历整个表,便可获取结果。

2. 带有意向锁的加锁协议

首先咱们要知道意向锁之间是互相兼容的

意向共享锁(IS) 意向排他锁(IX)
意向共享锁(IS) 兼容 兼容
意向排他锁(IX) 兼容 兼容

可是它是和普通的排他/共享锁互斥

意向共享锁(IS) 意向排他锁(IX)
共享锁(S) 兼容 互斥
排他锁(X) 互斥 互斥

SIX:表示这一子树的根结点是被共享锁锁住。而在低层次的结点(好比叶子结点)会加排他锁。

这里附上带有意向锁的兼容矩阵

image-20210407134745569

下面看一个有三个事物的例子

image-20210407131752453

假设按照顺序。T1有更高的优先级

image-20210407132015142

这里因为T1要扫描整个表同时更新一些tuple。因此这里咱们要对这个表加SIX

image-20210407132230287

对于事物T2。只读取一个tuple。因此对整个表+ IS锁。同时对本身读区的tuple。加S锁,固然若是这个tuple在以前被T1加了X锁的话则就必需要等待了⌛️

image-20210407132434799

随后对于事物T3。要扫描整个R。就要先等待了。由于整个表还有SIX锁。就表示有某些tuple。处于X锁

image-20210407132605817

image-20210407132657144

这里要等待对于整个表的SIX意向锁释放以后。就能够扫描整个表了。

简而言之意向锁的做用。就是能够实现表及锁和行级锁共存。

  1. 若是表X锁被占有,则其余事务尝试得到 ISIX均会阻塞,也就不能继续获取行X锁行S锁了.
  2. 若是表S锁被占有,则其余事务能够得到IS, 而得到IX会阻塞. 从而阻止其余事务得到行X锁

4. Lock Manager实现补充

1. Lock Table

这一部分是书上的内容。就是讲解锁管理机制是如何管理锁表的。

image-20210407133517539

是利用上图的链式哈希表来实现对于锁得管理。能够看到对于元素I23事物T一、T8正在持有锁。而事物T2正在等待锁。好比T一、T8正在读该元素所以它们两个都加了S锁。可是T2想要修改这个元素也就是T2想要加X锁。所以这里T2就必须等待T一、T8。S锁的释放。这里也是咱们lab4实现整个锁管理的主要结构

2. 插入、删除操做实现

以前咱们的注意力都在读操做和写操做上。下面咱们把目光汇集到插入和删除等操做上。

咱们用\(I_i\) 表示事物Ti的操做。用\(I_j\) 表示事物Tj的操做。Let \(I_i = delete(Q)\)。咱们考虑不一样种类的\(I_j\)

image-20210407142148610

对于插入操做。咱们没法对一个不存在的元素。进行读和写。在2PL协议中。插入操做就能够当作写操做。也就是说能够得到X锁。若是Ti要执行Insert(Q)操做。那么Ti会给新加入的元素Q一个X锁。

相关文章
相关标签/搜索