15-并发控制理论

并发控制横跨了多个层级:

  • operator Execution 操作执行
  • Access Methods 读表
  • buffer Pool Manager 缓存池

日志恢复

  • buffer Pool Manager 缓存池
  • Disk 磁盘管理

Motivation:

  • 当多人修改数据库同一条数据,就会出现竞争问题
  • 把100块钱从A账户转移到B账户,如果A账户扣100之后,机房断电了。当断电恢复之后,当前的数据库状态应该是什么?(如果没有恢复,状态肯定是错误)

image-20240727152035882

上面一个涉及丢失更新的问题 Lost Updates ,用并发控来解决。下面一个问题,用持久性来解决,用恢复来解决
并发控制和恢复是数据库非常重要的功能。他们是实现事务ACID特性的基础。
事务(Transcation 简称:txn) 是多个数据库操作组合成的一个不可分割的、同时成功或失败的工作单元。txn是数据库操作的基本单元,一个txn包含一个或者多个动作,
在操作数据的时候,如果没有显示开txn,数据库会隐式开txn。在数据库中一条sql也是一个txn。

image-20240727152157622

批: 关于存储过程的理解:

  • 存储过程,从表现上来看,也可以看成是一个大的事务。要么执行成功,要么执行失败。存储过程可以看成一个受到限制的函数。这也是其和其他图灵完备的语言,如C++,python函数的区别。
    转账包含3个动作:
  • 检查用户的账户是否有100美元
  • 用户的账户减去100美元
  • 目标账户增加100美元

这三条语句要么都成功,要么都失败。关于转账或者其他txn要实现的功能,其实用户完全可以自己编程实现。但是数据库提供了事务,保证了正确性。简化了这个操作,使得用户只关心目标。不用关心目标之外的其他影响。 产品和用户的界限越来越模糊,并没有明确的界限。

image-20240727152247865

最简单的实现txn思路:

  • 一次只执行一条(缺点:不能并发跑,影响数据库的性能)
  • 将整个数据复制一份,备份一份。在复制一份上进行修改,如果执行成功,就持久化,否则不操作(数据量太大)

ps:上面最简单的思路其实提供了两个并发拓展的方向,一个是从时间上角度,去实现并发控制,比如2阶段;一种是空间角度,去实现并发,比如多版本控制。

image-20240724205526198

并发:在同一时间间隔内有多个事件或活动发生,即在同一个时间段内,多个事务交替执行。 并发的好处:

  • 单条请求, 响应时间更短
  • 对系统更高的利用效率

image-20240727152725725

并发的要求

  • 正确性
  • 公平
    并发是为了提高txn的并行度,它的正确性有一个基准:即串行执行txn的结果。和串行执行txn结果一致即是正确。
    并发必须进行控制,如果不进行控制,可能会导致数据永久性损坏(比如写入脏数据)。

image-20240727152756018

多个txn能否并发,如何并发?如何交叉执行。这就是接下来要解决的问题,并发需要寻找一些标准。

image-20240727153027796

数据库不知道sql语句本身代表的业务的含义,所以数据库要判断txn之间能否交叉运行,如何交叉运行,要根据sql本身去分析。接下来,需要对txn进行简化,抽取txn最本质的特性,继而对其进行分析。

image-20240727153227567

  1. 用 data object 代表操作的目标
  2. 将事务的行为 简化成 读写操作
  3. 事务以BEGIN命令标示开始
  4. 事务的结束或者结果只有两种形态:COMMIT 或者 ABORT(ROLLBACK)。其中commit表示txn执行成功,提交;rollback表示txn回滚(部分系统用abort表示回村)。
  • 回滚指令可是程序本身发出的,也可以是数据库发出的

image-20240727153413407

回滚有可能是程序本身发出的,也有可能是数据库本身发出的。

比如当前的txn和其他的txn产生了dead clock,那么数据库会主动打断当前txn的进程,比如当前的txn和其他的txn产生了dead clock,那么数据库会主动打断当前txn的进程

并发要保证正确性,接下来介绍正确性的四个标准:ACID,即 数据库执行txn怎么才算正确执行了
这四个标准也是txn的四个性质,也可以看做并发的四个限制。

image-20240727153717645

原子性:要么执行成功,要么失败。

一致性:数据在执行txn之前和之后,状态要保持一致。数据来自于真实世界,反应真实世界,所以执行前后也要满足真实世界的逻辑。比如 A向B账户赚钱,A账户和B账户的钱 转账前后要保持一致。

隔离性:互不干扰。从结果上看,多个事务在并发执行的过程中所得到的结果,和串行执行得到的结果是一致;从用户角度来看,用户执行的操作和其他事务隔离。

持久性:一个txn一旦执行成功,其对数据的影响是持久的,不会丢失。 无论是否停电或者是否发生其他非可抗因素,一旦txn提交,那么其对数据的改变就是永久的。

通俗的版本:

image-20240727154343969

今天要研究四个方面如何实现?

image-20240727154434471

首先是原子性。
一个txn只有两种结局:成功,终止(被回滚)。

原子性由数据库保证,事务是数据库提供的服务。

  • 在用户看来, 你的指令要么全部执行,要么一个也不执行。
    image-20240727154453889

image-20240727154603521
两个场景发生之后,数据库当前这批数据的状态应该是什么? 事务要回滚到最原始的状态

  • 案例1:从账户A中取了100还没有转移到账户B中,数据库把事务终止了
  • 案例2:从账户A中取了100还没有转移到账户B中,停电了

image-20240727154720640
实现原子性的手段:

  • 日志

    • 执行一步,同时在日志中,记录如果回滚,该如何操作,即回滚日志,Undo log
    • 一般是在内存和磁盘中都会存日志
    • 日志好像是飞机的黑匣子一样
      • 日志不光有回滚txn的作用,还有审计和提高执行效率的作用。
      • 审计:如果数据库出问题,查看是哪些操作触发了审计。
      • 提高效率:即先记录,在当时并不是真的执行,而后再在合适的时候,执行。 lazy evaluation
  • 增量备份:只备份你修改的page。今天这个技术已经被淘汰了。
    image-20240727155142523

一致性:
txn执行前后,要满足基本的真实世界的逻辑。事务的执行会改变系统状态,一致性是指事务的执行会保证数据库从一个正确(一致)的状态转移到另一个正确的状态。

  • 比如数据库可以存储负数,但银行业务中用户的存款不能出现负数,存在负存款账户的数据库状态是不正确的状态
    一致性分为:
  • 数据库一致性
  • 事务一致性

image-20240727155249139

数据库一致性

  • 数据库是对真实世界的建模,其必须满足完整性约束。(什么是完整性约束?)
  • 未来的事务操作 要能看到 历史的事务的影响(?)
  • 如果在txn执行之前,数据库满足一致性,那么在txn执行之后,数据库也应该满足一致性(数据库满足一致性是什么意思?)

image-20240727155432397

事务的一致性:要靠业务来保证,数据库无法保证(一致性其实完全可以由用户编程实现保证)。

image-20240727155523262

PS:关于事务的并发,可以建模成一个带约束的优化问题:

  • 初版: 最大化事务的并发量 在 公平性和正确性的限制下
  • 二版: 最大化事务的并发两 在 ACID的限制下

隔离性
事务的隔离性是指多个事务在并发执行的过程中所得到的结果,和串行执行得到的结果是一致的,保证了事务并发处理中事务的正确
理想的隔离:在一个时间只运行一个txn。在后面我们会看到:并发控制是实现隔离性的主要手段。
为什么要实现隔离性?

  • 为了提高并发度,同时也方便了使用者,写sql的人。串行处理不存在事务之间的相互干扰,但是不能充分利用计算资源,效率低下。
  • 用户在进行转账操作的时候,直接写转账的sql,能不能操作成功,数据库来做检查。(数据库做隔离性,就是为了用户能更好的实现业务)
    并发控制可以提升事务并发性,但是要解决冲突问题

image-20240727155853319

所以,我们需要一种方法来交替穿插的跑txn。这里面有一个基准:数据库角度肯定是多个txn一起执行的,但是对用户来说要像串行跑一样。

接着从实际的执行效果,对txn进行分类,看看那些类可以进行直接跑,哪些类可以转化为串行跑。(比如如果多个事务都只有读操作,这个完全可以并发)

并发控制协议是决定多个txn如何交替,按照什么顺序执行?什么时候让txn失败

image-20240727160251678

有两大流派:

  • 悲观派:事情还未发生之前做控制,让坏的事情不要发生,即事前控制,比如 后续介绍的两阶段锁
  • 乐观派:事情已经发生了,再去消除影响,即事后控制。先发展,后治理。矫枉必过正。出问题了再去让出问题的txn回滚掉,比如 基于时间戳并发控制

乐观比悲观好。实际中,应该是乐观悲观并用。

image-20240727161046089

如何实现并发?

image-20240727161132160

从结果分析:如何才算正确的输出。因为无论如何执行,要保证结果都是正确的。正确的基准是什么?是串行执行的结果。
只要是和串行执行的结果相符合,那么就是正确的执行。并发的目标就是同时运行多个事务但是和串行的效果是一样的。

image-20240727161149723

并没有说,T1一定要比T2先执行,或者 T2一定要比T1先执。但是运行的结果一定要和串行运行的结果相同。

image-20240727161331267

image-20240727161340349

从结果来看,上述两个事务顺序无论谁先执行结果都是正确的。上述叫真正顺序的执行。

如果是真的并发过来的,那么怎么让它交叠的去跑?interleave(交插)

交替跑是一个优化问题,优化目标是最大并发量,约束条件是什么?是公平,正确。

交叠跑:

  • 最大化系统并发量
  • 更好的利用多核cpu(系统可用性更高,能更加充分的利用系统)

如果一个txn出错了,那么它不会影响别的txn
image-20240727162157216

如果对并发不进行控制,如下就是随机跑的案例。(这页PPT有问题)

image-20240729185354426

如下这种执行顺序就是错误的,存在一致性问题。

image-20240729185501450

转换成数据库的操作,数据库在干什么?

对数据库而言,所有的操作落实下去无非是读写两个操作。所以后续用读写操作为工具,来分析并发中可能遇到的种种问题,而后对种种问题进行解决。

image-20240729185635023

使用读操作和写操作来描述事务的执行过程。后面分析问题的形式就要发生变化了。变成读写角度了。

我们可以从人为的角度,判定txn的执行是否存在问题,那么我们如何编写程序,让数据库来识别出txn的执行是否存在问题?

以真正串行的调度作为基准,如果实际txn的执行可以等价成串行,那么没有问题。

image-20240729190035520

那么如何等效?

image-20240729190054811

ps:这里面几个改变,serial schedule(串行化调度),Equivalent Schedules(等价调度),Serializable Schedule(可串行化调度)的概念

  1. 串行调度(Serial Schedule): 事务串行执行。所谓 Serial Schedule,就是 针对不同的事务,完全串行执行,没有交叉执行。

  2. 等价调度(Equivalent Schedules):

    • 针对同一个事务的两个调度,如果调度1和调度2对数据库的影响是一致的,那么这两个调度是等价的
  3. 可串行化调度(Serializable Schedule)

  • 给定一个并发调度S ,存在一个串行调度S’, 在任何数据库状态下,按照调度S 和调度S’执行后所产生的结果都是相同的 。
  • 此时调度S 被称之为可串行化调度(serializable schedule)。

4.可串行化调度的数量十分巨大,且难以校验,数据库中一般通过找到可串行化调度的子集(充分条件),即找到能够提前确认是可串行调度的并发调度,进而提升调度效率。

image-20240729190224634

可串行化调度 。如果每一个txn都保证一致性,那么可串行化调度也保证了一致性。

image-20240729190412730

可串行化是一个不太好理解的概念。

更大的灵活性意味着更大的并行度。

  1. 如何判断一个调度是否可串行化?如何实现可串行化调度?
  2. 验证代价较大,一般找一个容易验证的可串行化调度方案,一般有如下几种方案:
  • 终态可串行化:判读最终执行结果的状态
  • 冲突可串行化:判断操作之间是否冲突
  • 视图可串行化

3.下面重点介绍的是冲突串行化

image-20240729190644734

冲突操作的定义:两个操作是冲突的,其需要满足如下条件:

  • 两个操作来自不同的事务
  • 针对在相同的object上,至少有一个是写

根据上面的定义,冲突有如下三种:(如下三种都是至少有一个写)

  • 读写冲突
  • 写读冲突
  • 写写冲突

image-20240729190851569

接下来,逐一的看:

读写冲突:一个事务读了两次,另外一个事务T2 在读的中间写了一次,此时出现的前后不一致现象称之为 读写冲突,又叫不可重复读。

image-20240729191130823

读写冲突 明显破坏了隔离性,第一次读和第二次读不一样。

写读冲突:(先写后读,而后撤销写操作)一个事务读取了未提交的版本修改叫脏读

image-20240729191232743

写写冲突:结果交叉。

交叉之后,串行的执行,会出问题。

串行的结果,要么最后A和B是T1写的结果;要么是T2写的结果;这两者任意一个结果都是正确的。不能是两者混着

image-20240729191446759

研究冲突是为了更好的串行化。

可串行化有两种方式:

  • Conflict Serializability(冲突可串行化)
  • view Serializability

image-20240729191757487

冲突可串行化调度,是从冲突的角度出发,针对一个调度S 去发现其等价的串行调度S’来确定S 是一个可串行化调度

  • 一次操作交换定义为交换事务调度序列中相邻的两个操作,一个交换操作可以将一个调度A变成另外一个调度B。

当交换不会影响两个调度的一致性时,定义该交换得到的两个调度是等价的,该交换为等价交换

  • 等价操作
    • 交换连续两个相同数据读取操作的顺序:RT1(A) RT2(A)
    • 交换连续两个不同数据读写操作的顺序:RT1(A) WT2(B);WT1(A) RT2(B);WT1(A) WT2(B)
  • 非等价操作
    • 交换连续两个相同数据的读写、写写操作: RT1(A) WT2(A);WT1(A) RT2(A);WT1(A) WT2(A)

两个调度是冲突等价的,当且仅当:(无法翻译)

  • 如果

一些冲突操作可以转换为 串行化操作

ex:不懂这里

image-20240729191924743

S是冲突等效的,当你可以将s变换成串行调度通过交换不同txn的连续非冲突操作。

image-20240729191951843

判断如下是否能可以转化成可串行化序列

image-20240729192246380

为什么可以滑动?因为不同的action是不存在。

在时间上不同的操作可以滑动位置。(ps:滑动位置只是说明可以串行化,那么实际执行也是串行化吗?)

image-20240729192337763

一个反例:

image-20240729192422160

一旦有重复,写些冲突,不能等效交换。没办法转化成真正串行的。

如果有冲突,不能等效交换位置。

image-20240729192507898

如下也不能交换位置:

image-20240729192609541

除了如上的三种冲突,都是可以交换位置的。

image-20240729192736213

当存在多个txn的时候,交换操作就会变得很难,所以需要新的算法。

引出依赖图这个工具:

image-20240729192901745

构建依赖图。(感觉拓扑排序可能在这里有用)

如果依赖图有环,永远无法等效成串行化。构建依赖图,判断是否有环。

  • 对于有向无环图的判定,可以使用经典的拓扑排序算法。若图G 无环,使用拓扑排序可以得到图G 的一个拓扑序列C。
  • 拓扑序列C 为该调度S’等价的串行调度S

image-20240729193237973

如果两个调换位置,那么就和串行化之后结果不同的。

image-20240729193316323

因为图中不存在环,所以可以先执行T2,再执行T2,再执行T3。

如上三个可串行化。不是真串行化,是执行效果相当于串行化。

如下是一个反例:永远没有通过滑动的办法,将如下操作等效为串行化。

image-20240729193730056

好像给了一个依赖图无法包含的问题。(我不懂这个案例放在这里的含义)

前面的是基于冲突的,下面是基于观察的。

image-20240729193929046

依赖图显示不可以,但是根据实际的业务发现是可串行的。依赖图是有局限性的,依赖图会错杀一些真正可串行化的情况

image-20240729194059418

image-20240729194109267

基于人类 观察的可串行化 比 基于冲突的 局限性更少。但是无法实现。

两种都没办法做到100%的不误杀,因为算法没办法理解业务。多少都会存在错杀的情况。

image-20240729194327127

实际中,基于冲突的用的更多,因为更好实现。错杀的特殊情况有些可以形成案例,摘出来,单独判断。(大模型套小模型,大模型+规则)

大部分数据库对于只读txn,那么就不需要判断读写依赖,写读依赖。直接并发就可以了。

多种调度并发执行分类:

  • 真正串行的是一小部分,稍微多点的是 冲突可串行化。再多的是观察可串行化。

image-20240729194710449

持久性:

image-20240729194722574
持久性:一旦某个事务提交以后,即使数据库发生故障,该事务的执行结果也不会丢失,可以被正确地恢复,仍然对后续事务可见。

实现方法:

  • Redo log 重做日志
  • shadow page 技术

** 回顾 & 总结**
image-20240729194808299

image-20240729194824350

  1. 并发控制和恢复是数据库最重要的功能之一。mysql是免费开源的数据库支持事务。

  2. 事务可以极大的减轻业务的实现复杂度。

  3. 并发控制是自动实现的。并发控制要保证:并发执行的效果和单独的串行执行效果相同

  4. google专家建议:大部分情况下,能用事务尽量用事务,但是某些地方可能直接coding性能更好,因为数据库不理解业务。

image-20240729195118391

posted @   金字塔下的蜗牛  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示