学习笔记 | 事务隔离性的实现——常见的并发控制技术MVCC

  • MVCC是一种多版本并发控制机制,是MySQL的InnoDB存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。MVCC是通过保存数据在某个时间点的快照来实现该机制,其在每行记录后面保存两个隐藏的列,分别保存这个行的创建版本号和删除版本号,然后Innodb的MVCC使用到的快照存储在Undo日志中,该日志通过回滚指针把一个数据行所有快照连接起来。

2.3 事务隔离性的实现——常见的并发控制技术

  • 并发控制技术实现事务隔离性以及不同隔离级别的关键,实现方式有很多,按照其对可能冲突的操作采取的不同策略可以分为乐观并发控制和悲观并发控制两大类。
  • 乐观并发控制:对于并发执行可能冲突的操作,假定其不会真的冲突,允许并发执行,直到真正发生冲突时才去解决冲突,比如让事务回滚
  • 悲观并发控制:对于并发执行可能冲突的操作,假定其必定发生冲突,通过让事务等待(锁) 或者中止(时间戳排序) 的方式使并行的操作串行执行。
2.3.1 基于封锁的并发控制

核心思想:对于并发可能冲突的操作,比如读-写,写-读,写-写,通过锁使它们互斥执行

锁通常分为共享锁和排他锁两种类型
  1. 共享锁(S): 事务T对数据A加共享锁,其他事务只能对A加共享锁但不能加排他锁。
  2. 排他锁(X): 事务T对数据A加排他锁,其他事务对A既不能加共享锁也不能加排他锁
基于锁的并发控制流程:
  • 事务根据自己对数据项进行的操作类型申请相应的锁(读申请共享锁,写申请排他锁)。
  • 申请锁的请求被发送给锁管理器。锁管理器根据当前数据项是否已经有锁以及申请的和持有的锁是否冲突决定是否为该请求授予锁。
  • 若锁被授予,则申请锁的事务可以继续执行;
  • 若被拒绝,则申请锁的事务将进行等待,直到锁被其他事务释放。
可能出现的问题:
  • 死锁: 多个事务持有锁并互相循环等待其他事务的锁导致所有事务都无法继续执行。
  • 饥饿: 数据项A一直被加共享锁,导致事务一直无法获取A的排他锁。
  • 对于可能发生冲突的并发操作,锁使它们由并行变为串行执行,是一种悲观的并发控制。
2.3.2 基于时间戳的并发控制

核心思想: 对于并发可能冲突的操作,基于时间戳排序规则选定某事务继续执行,其他事务回滚。

  • 系统会在每个事务开始时赋予其一个时间戳,这个时间戳可以是系统时钟也可以是一个不断累加的计数器值,当事务回滚时会为其赋予一个新的时间戳,先开始的事务时间戳小于后开始事务的时间戳。

每一个数据项Q有两个时间戳相关的字段:

  • W-timestamp(Q): 成功执行write(Q)的所有事务的最大时间戳
  • R-timestamp(Q): 成功执行read(Q)的所有事务的最大时间戳
时间戳排序规则如下:
  • 假设事务T发出read(Q),T的时间戳为TS。
    a. 若TS(T)<W-timestamp(Q),则T需要读入的Q已被覆盖。此read操作将被拒绝,T回滚。
    b. 若TS(T)>=W-timestamp(Q),则执行read操作,同时把R-timestamp(Q)设置为TS(T)R-timestamp(Q)中的最大值。

  • 假设事务T发出write(Q)
    a.若TS(T)<R-timestamp(Q),write操作被拒绝,T回滚。
    b.若TS(T)<W-timestamp(Q),则write操作被拒绝,T回滚。
    c.其他情况:系统执行write操作,将W-timestamp(Q)设置为TS(T)

基于时间戳排序和基于锁实现的本质一样:对于可能冲突的并发操作,以串行的方式取代并发执行,因而它也是一种悲观并发控制。它们的区别主要有两点:
  • 基于锁是让冲突的事务进行等待,而基于时间戳排序是让冲突的事务回滚。
  • 基于锁冲突事务的执行次序是根据它们申请锁的顺序,先申请的先执行;
  • 基于时间戳排序是根据特定的时间戳排序规则。
2.3.3 基于有效性检查的并发控制
  • 核心思想: 事务对数据的更新首先在自己的工作空间进行,等到要写回数据库时才进行有效性检查,对不符合要求的事务进行回滚。
基于有效性检查的事务执行过程会被分为三个阶段:
  • 读阶段: 数据项被读入并保存在事务的局部变量中。所有write操作都是对局部变量进行,并不对数据库进行真正的更新。
  • 有效性检查阶段: 对事务进行有效性检查,判断是否可以执行write操作而不违反可串行性。如果失败,则回滚该事务。
  • 写阶段: 事务已通过有效性检查,则将临时变量中的结果更新到数据库中。
  • 有效性检查通常也是通过对事务的时间戳进行比较完成的,不过和基于时间戳排序的规则不一样。
  • 该方法允许可能冲突的操作并发执行,因为每个事务操作的都是自己工作空间的局部变量,直到有效性检查阶段发现了冲突才回滚。因而这是一种乐观的并发策略。
2.3.4 基于快照隔离的并发控制
  • 快照隔离是多版本并发控制(mvcc) 的一种实现方式。
  • 其核心思想是: 数据库为每个数据项维护多个版本(快照),每个事务只对属于自己的私有快照进行更新,在事务真正提交前进行有效性检查,使得事务正常提交更新或者失败回滚。
  • 由于快照隔离导致事务看不到其他事务对数据项的更新,为了避免出现丢失更新问题,可以采用以下两种方案避免:
  • 先提交者获胜: 对于执行该检查的事务T,判断是否有其他事务已经将更新写入数据库,是则T回滚否则T正常提交。
  • 先更新者获胜: 通过锁机制保证第一个获得锁的事务提交其更新,之后试图更新的事务中止。
  • 事务间可能冲突的操作通过数据项的不同版本的快照相互隔离,到真正要写入数据库时才进行冲突检测。因而这也是一种乐观并发控制。
2.3.5 关于并发控制技术的总结
  • 并发控制的实现的方式太过多样,基于封锁的实现就有很多变体,mvcc多版本并发控制的实现方式就更是多样,而且很多时候会和其他并发控制方式比如封锁的方式结合起来使用。

3. 故障与故障恢复技术

3.1 为什么需要故障恢复技术

数据库运行过程中可能会出现故障,这些故障包括事务故障和系统故障两大类。

  • 事务故障: 比如非法输入,系统出现死锁,导致事务无法继续执行。
  • 系统故障: 比如由于软件漏洞或硬件错误导致系统崩溃或中止。

这些故障可能会对事务和数据库状态造成破坏,因而必须提供一种技术来对各种故障进行恢复,保证数据库一致性,事务的原子性以及持久性。数据库通常以日志的方式记录数据库的操作从而在故障时进行恢复,因而可以称之为日志恢复技术

3.2 事务的执行过程以及可能产生的问题

在这里插入图片描述

事务的执行过程可以简化如下:
  • 系统会为每个事务开辟一个私有工作区。
  • 事务读操作将从磁盘中拷贝数据项到工作区中,在执行写操作前所有的更新都作用于工作区中的拷贝。
  • 事务的写操作将把数据输出到内存的缓冲区中,等到合适的时间再由缓冲区管理器将数据写入到磁盘。
由于数据库存在立即修改和延迟修改,所以在事务执行过程中可能存在以下情况:
  • 在事务提交前出现故障,但是事务对数据库的部分修改已经写入磁盘数据库中。这导致了事务的原子性被破坏。
  • 在系统崩溃前事务已经提交,但数据还在内存缓冲区中,没有写入磁盘。系统恢复时将丢失此次已提交的修改。这是对事务持久性的破坏。
3.3 日志的种类和格式
<T,X,V1,V2>:描述一次数据库写操作,T是执行写操作的事务的唯一标识,X是要写的数据项,V1是数据项的旧值,V2是数据项的新值。
<T,X,V1>:对数据库写操作的撤销操作,将事务T的X数据项恢复为旧值V1。在事务恢复阶段插入。
<T start>: 事务T开始
<T commit>: 事务T提交
<T abort>: 事务T中止

关于日志,有以下两条规则

  1. 系统在对数据库进行修改前会在日志文件末尾追加相应的日志记录。
  2. 当一个事务的commit日志记录写入到磁盘成功后,称这个事务已提交,但事务所做的修改可能并未写入磁盘。
3.4 日志恢复的核心思想
  • 撤销事务undo: 将事务更新的所有数据项恢复为日志中的旧值,事务撤销完毕时将插入一条记录。

  • 重做事务redo: 将事务更新的所有数据项恢复为日志中的新值。

  • 事务正常回滚/因事务故障中止将进行redo。

  • 系统从崩溃中恢复时将先进行redo再进行undo

  • 以下事务将进行undo: 日志中只包括<T start>记录,但既不包括<T commit>记录也不包括记录。

  • 以下事务将进行redo: 日志中包括<T start>记录,也包括<T commit>记录或<T abort>记录。

假设系统从崩溃中恢复时日志记录如下
<T0 start>
<T0,A,1000,950>
<T0,B,2000,2050>
<T0 commit>
<T1 start>
<T1,C,700,600>
  • 由于T0既有start记录又有commit记录,将会对事务T0进行重做,执行相应的redo操作。
  • 由于T1只有start记录,将会对T1进行撤销,执行相应的undo操作,撤销完毕将写入一条abort记录。
3.5 事务故障中止/正常回滚的恢复流程
  • 从后往前扫描日志,对于事务T的每个形如<T,X,V1,V2>的记录,将旧值V1写入数据项X中。
  • 往日志中写一个特殊的只读记录<T,X,V1>,表示将数据项恢复成旧值V1,
    这是一个只读的补偿记录,不需要根据它进行undo。
  • 一旦发现了日志记录,就停止继续扫描,并往日志中写一个<T abort>日志记录。

在这里插入图片描述

3.6 系统崩溃时的恢复过程(带检查点)
  • 检查点是形如的特殊的日志记录,L是写入检查点记录时还未提交的事务的集合,系统保证在检查点之前已经提交的事务对数据库的修改已经写入磁盘,不需要进行redo。检查点可以加快恢复的过程。
  • 系统奔溃时的恢复过程分为两个阶段:重做阶段和撤销阶段。
重做阶段:

系统从最后一个检查点开始正向的扫描日志,将要重做的事务的列表undo-list设置为检查点日志记录中的L列表。

发现<T,X,V1,V2>的更新记录或<T,X,V>的补偿撤销记录,就重做该操作。

发现记录,就把T加入到undo-list中。

发现或记录,就把T从undo-list中去除。

撤销阶段:

系统从尾部开始反向扫描日志发现属于undo-list中的事务的日志记录,就执行undo操作

发现undo-list中事务的T的记录,就写入一条记录,并把T从undo-list中去除。

4.undo-list为空,则撤销阶段结束

  • 总结: 先将日志记录中所有事务的更新按顺序重做一遍,在针对需要撤销的事务按相反的顺序执行其更新操作的撤销操作。
3.6.1 一个系统崩溃恢复的例子

恢复前的日志如下,写入最后一条日志记录后系统崩溃

<T0 start>
<T0,B,2000,2050>
<T2 commit>
<T1 start>
<checkpoint {T0,T1}>   //之前T2已经commit,故不用重做
<T1,C,700,600>
<T1 commit>
<T2 start>
<T2,A,500,400>
<T0,B,2000>
<T0 abort>   //T0回滚完成,插入该记录后系统崩溃

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值