为了账号安全,请及时绑定邮箱和手机立即绑定

两段封锁、三级封锁、多粒度封锁

标签:
MySQL

引言
现在数据库的实现已经很成熟了,基本已经能够满足我们日常的工作和学习所需,但是有的时候,人还是要“犯贱”一下嘛,理解数据库系统是怎么保证我们的事务操作的一致性(当然也有不一致的情况,大不了全部回滚嘛)

  • 文章有点长,慢慢食用~

废话不多说,本篇文章从以下三个方面,深入理解数据库系统的锁的机制,如果有不正确的地方,还请留言告知

  • 三级封锁
  • 两段封锁
  • 多粒度封锁

锁的由来

数据库(DB),数据库系统(DBMS) 相信大家都比我还清楚,不清楚的可以去找度娘...

  • 举个栗子
    人物:小明 (常出现于初中作文、英语、数学题、物理题、化学题, 这次也有幸在我们的数据库出现)
    任务:开商店

    webp

    没错,就是我

情景一
-- 时间:电脑还未普及的年代

  • 需求:记账

  • 解决方案:
    给他一支笔,一个本子(有条件就给专门记账本,没条件给一张纸);
    记录内容:日期,天气,时间,购买人,购买物品,数量,单价,实收;
    (好了,够他玩一阵子了)

  • 缺点:适应现阶段的需求

情景二
-- 时间:电脑普及

  • 需求:电脑记账(对的,你没看错,小明就是这么潮)

  • 解决方案:
    1.创建一个记账.txt,好了,拿去玩吧;
    2.使用数据库,创建字段,进行录入每笔交易记录;

  • 小明现在就一个店铺,一台电脑,所以,他自己去玩吧...

情景三
--- 时间:电脑不值钱,并且每件物品的库存总数变化要在DB中有所展示

  • 需求:连锁商店自动化记账,并且录入一个数据库(假设当前每个店铺只有一台电脑)

  • 解决方案1:

  1. 给每个连锁店的电脑安装数据库,并且每台电脑之间的数据互不相通;

  2. 每台电脑各自记录,并将当前数据记录到各自的DB中;

  3. 固定时间,进行店铺汇总;

  • 解决方案2:

  1. 建立云端的数据库,每个连锁店铺的电脑对云端的数据库进行读写;

  2. 可以实时读写,即李梅买了一桶油,付钱完成,就与远端服务器进行链接并进行记录;也可以先记录到本地,每天固定时间,各个店铺对远端服务器上面的DB进行事务操作;

情景四
-- 时间:小明连锁商店变成了连锁超市

  • 需求:每间店铺内的结算电脑不只一台

  • 解决方案:
    暂时不是本片文章讨论范围,以后会在其他文章内进行给出,敬请谅解



好了,至此,已经可以大致了解了小明同学一路的创业之路,我们要好好向他学习成功的经验,要从他....
(咳咳,走错片场了....┭┮﹏┭┮)

下面我们来逐步分析各个情景:
1.对于情景一、二,我们提供的解决方案基本可以满足小明同学的需求了,至于他有什么界面美观呀,操作便捷呀之类的需求,一概无视,我是程序员,又不是美工;[・`Д´・ ]
2.对情景三而言,问题比较严肃。我们需要对每台电脑同步数据到云端的数据库,进行控制,否则会出现写入异常问题,特别是对总数进行增减。

webp




假设现在有两台电脑
1.T1,T2
2.DB中A的数量为100
他们都需要对数据库中的商品A进行数量修改
T1 今天卖出了30件
T2今天进货60件,卖出去70件


操作如表1

TimeT1T2A
time1Read(A)---T1-A: 100 T2-A: 0 DB-A: 100
time2----Read(A)T1-A: 100 T2-A: 100 DB-A: 100
time3Write(A - 30) Commit(A)---T1-A: 70 T2-A: 100 DB-A: 70
time3----Write(A + 60)T1-A: 70 T2-A: 160 DB-A: 70
time4----Write(A - 70)T1-A: 70 T2-A: 90 DB-A: 70
time5----Commit(A)T1-A: 70 T2-A: 90 DB-A: 90

显然,在不经控制的时候,T1和T2对数据的操作是存在很大的问题的,那么我们该如何解决呢?
...

串行,先执行T1,当T1执行完毕后,再执行T2,或者两者交换顺序。

这是一个不错的解决方法,但是对于小明这样的,动不动就喜欢开连锁店的人而言,你可以顺序执行10家,100家,但是对于1000家,10000家呢?(不要以为小明不会开这么多店铺,他可是小明呀o(╥﹏╥)o)

我们这个时候要实现串行怎么办呢?要保证一台电脑执行完了,在执行另一台电脑。这就是下面我们将要提到的两段封锁机制

  • 在此之前,我们要明白我们为什么要串行执行任务,是因为串行执行可以保证数据库A的值安全;

  • 但是为什么串行就可以保证数据库A的值是安全的呢?

  • 这里我们将对数据的操作可以分为Read和Write两个操作;

  • 表1中,T1读取了A的值,还没来得及对A进行写的时候,T2也对A进行了读取;

  • 那我们要阻止在T2读取数据A的时候,T1对A进行写;或者在T1对数据A写的时候,不允许T2进行读写;

是不是晕了?没事,吸两口,就能懂,很简单的


webp

吸两口

有了上述的要求,那我们是不是可以创建锁,对A进行加锁,

  1. 当读取的时候添加读锁(共享锁,share,这个名字真的不是我随意取的);

  2. 当写入的时候添加写锁(排它锁,x锁,这个名字也不是我随意取的)

我们要求:

  • 在读取的时候,别的事务不能对其进行写操作,但是可以读操作;

  • 在写取的时候,别的事务不能对其进行读、写操作;
    具体的情况我们可以用表2来展示

S(share-lock)X(x-lock)no-lock
S(share-lock)
X(x-lock)

好了,我们对造成表1的原因进行的分析,并进行了限制,现在我们再来模拟一下表1中的执行(这里我们假设锁的释放为用完即释放,Eg:S-lock(A) -> Read(A) -> S-Unlock(A))
表3

TimeT1T2A
time1S-lock(A)---T1-A: 0 T2-A: 0 DB-A: 100
time2Read(A)---T1-A: 100 T2-A: 0 DB-A: 100
time3S-unlock(A)---T1-A: 100 T2-A: 0 DB-A: 100
time4---S-lock(A)T1-A: 100 T2-A: 0 DB-A: 100
time5----Read(A)T1-A: 100 T2-A: 100 DB-A: 100
time6---S-unlock(A)T1-A: 100 T2-A: 100 DB-A: 100
time7X-lock(A)---T1-A: 100 T2-A: 100 DB-A: 100
time8Write(A - 30) Commit(A)---T1-A: 70 T2-A: 100 DB-A: 70
time9X-unlock(A)---T1-A: 100 T2-A: 100 DB-A: 100
time10---X-unlock(A)T1-A: 70 T2-A: 100 DB-A: 70
time11----Write(A + 60)T1-A: 70 T2-A: 160 DB-A: 70
time12----Write(A - 70)T1-A: 70 T2-A: 90 DB-A: 70
time13----Commit(A)T1-A: 70 T2-A: 90 DB-A: 90
time14---X-unlock(A)T1-A: 70 T2-A: 90 DB-A: 90

WTF为啥还不行...

webp

Why?


  • 看样子,仅仅只是对数据项添加对应的锁是不行的,那是锁的问题吗?还是加锁的机制有问题?

  • (装逼时刻)我明确的告诉你,锁是没错的,我们要在加锁的时候,采用一定的策略,这样就能实现我们所要达成的问题

接下来,我们再考虑一下,什么策略可以保证各个计算机在并行对数据库进行操作的时候,可以确保其结果和某一个串行的执行顺序一致?

  • 可能有人会说,为什么是某一种串行,因为就现在小明连锁店的情况而言,可能只需要对DB中的数据进行加减即可;但是不能保证每次的执行的事务都是加减法吧。2333

咱们引入一个概念串行调度

  • 多个事务进行操作时,如果以事务为单位,多个事务依次执行

可串行化

  • 对于一个并发事务集来说,如果一个调度与同一事务集中某一个串行调度等价,则称该调度为可串行化

webp

讲人话

  • 简单的来讲就是,在并行调度的时候,我们要确保并发执行的操作事务,与某一种事务串行的结果一致;

  • 至于课串行化,就是在并行调度的过程中,对原子事务进行交换,且不改变原并行调度执行结果,可以得到一个串行调度的队列

有了以上的知识,明确了我们需要一种机制来保证事务的并发执行与串行调度一致,介绍以下封锁机制


两段封锁

首先搬出各大书中对其的定义(定义嘛,看不懂直接跳过)

  • 扩展阶段:申请并获得各种类型的锁。此阶段只能申请事务中需要的锁,但是不能释放锁

  • 收缩阶段:释放所有申请的锁。此阶段只能释放该事务申请的锁,且不能再申请锁

  • 简单的来讲就是,在一个事务中所有的封锁操作必须出现在第一个释放锁的操作之前

Eg:

  • T Slock(A) Slock(B) Slock(C)    第一阶段 扩展阶段

  • T Unclock(B) Unclock(C) Unclock(A) 第二阶段 收缩阶段
    不正确的例子

  • Slock(A) Slock(B) Unclock(B) Slock(C) Unclock(C) Unclock(A)

webp

thinking...

下面我们通过表4来演示一遍两段封锁

TimeT1T2A
time1S-lock(A)---T1-A: 0 T2-A: 0 DB-A: 100
time2Read(A)---T1-A: 100 T2-A: 0 DB-A: 100
time3X-lock(A)---T1-A: 100 T2-A: 0 DB-A: 100
time4---S-lock(A)T1-A: 100 T2-A: 0 DB-A: 100
time5---Wait()T1-A: 100 T2-A: 0 DB-A: 100
time6S-unlock(A)Wait()T1-A: 100 T2-A: 0 DB-A: 100
time7Write(A-30)Wait()T1-A: 70 T2-A: 0 DB-A: 100
time8Commit()Wait()T1-A: 70 T2-A: 0 DB-A: 70
time9X-lock(A)Wait()T1-A: 70 T2-A: 0 DB-A: 70
time10---Read(A)T1-A: 70 T2-A: 70 DB-A: 70
time11---X-lock(A)T1-A: 70 T2-A: 70 DB-A: 70
time12---Write(A+60)T1-A: 70 T2-A: 130 DB-A: 70
time13---Write(A-70)T1-A: 70 T2-A: 60 DB-A: 70
time14---Commit()T1-A: 70 T2-A: 60 DB-A: 60
time15---S-unlock(A)T1-A: 70 T2-A: 60 DB-A: 60
time16---X-unlock(A)T1-A: 70 T2-A: 60 DB-A: 60

终于对了,按照两段封锁协议,T1和T2的并行操作没有问题
但是,However他们只是对一个数据进行修改呀,要是对DB中多个数据进行修改呢?


假设:DB中现有三个商品分别为A:30  B:60 C:90

  1. T1:  A: -10 B: + 30 C:-7

  2. T2: C:-10 A: -20 B: + 20
    我们依旧采用两段封锁来做实验,如表5

TimeT1T2
time1S-lock(A)---
time2Read(A)S-lock(C)
time3X-lock(A)Read(C)
time4S-lock(B)S-lock(A)
time5Write(A-10)Wait(A)
time6X-lock(B)---
time7Write(B+30)---
time8S-lock(C)---
time9Write(C)---
time10------

额,死锁了...
由于两段封锁协议的定义,T1在未对C进行加锁和操作前,不能对A,B进行释放;同样的,T2在没有对A,B进行加锁操作前,不能释放C,这就导致了死锁。

由此可知,两段封锁协议,虽然可以从并发调度的角度上面将事务进行串行化,以保证其结果满足一种串行调度,但是仍然无法有效的排除死锁问题。


三级封锁

已经理解了两段加锁,对于现在要讲的三级封锁,理解上就会很容易

webp

  • 一级封锁协议:事务T在对数据D进行写操作之前,必须对D加X锁,保持加锁状态直到事务操作结束;

  • 二级封锁协议:事务T在读取数据D之前必须先对D加S锁,在读完之后即刻释放加在D上的S锁;与一级封锁协议一起构成二级封锁协议

  • 三级封锁协议:事务T在对数据D读之前必须先对D加S锁,直到事务结束才能释放加在D上的S锁;与一级和二级封锁协议构成三级封锁协议



作者:jufengliushao
链接:https://www.jianshu.com/p/59d87e5fd63d


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消