热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

CMU15445LAB3:事务隔离,twophaselocking,锁管理器

概述本lab将实现一个锁管理器,事务通过锁管理器获取锁,事务管理器根据情况决定是否授予锁,或是阻塞等待其它事务释放该锁。背景事务属性众所周知,事务具有如下属性:原子性:事务要么执行

概述

本lab将实现一个锁管理器,事务通过锁管理器获取锁,事务管理器根据情况决定是否授予锁,或是阻塞等待其它事务释放该锁。


背景

事务属性

众所周知,事务具有如下属性:



  1. 原子性:事务要么执行完成,要么就没有执行。

  2. 一致性:事务执行完毕后,不会出现不一致的情况。

  3. 隔离性:多个事务并发执行不会相互影响。

  4. 持久性:事务执行成功后,所以状态将被持久化。


一些定义

将对数据对象Q的操作进行抽象,read(Q):取数据对象Q,write(Q)写数据对象Q。


schedule

考虑事务T1,T1从账户A向账户B转移50。

T1:
read(A);
A := A - 50;
write(A);
read(B);
B := B + 50;
write(B).

事务T2将账户A的10%转移到账户B。

T2:
read(A);
temp := A * 0.1;
A := A - temp;
write(A);
read(B);
B := B + temp;
write(B).

假设账户A、B初始值分别为1000和2000。

我们将事务执行的序列称为schedule。如下面这个schedule,T1先执行完,然后执行T2,最终的结果是具有一致性的。我们称这种schedule为serializable schedule

T1 T2
read(A);
A := A - 50;
write(A);
read(B);
B := B + 50;
write(B).
read(A);
temp := A * 0.1;
A := A - temp;
write(A);
read(B);
B := B + temp;
write(B).

但是看下面这个shedule:

T1 T2
read(A);
A := A - 50;
read(A);
temp := A * 0.1;
A := A - temp;
write(A);
read(B);
write(A);
read(B);
B := B + 50;
write(B).
read(B);
B := B + temp;
write(B).

执行完账户A和B分别为950和2100。显然这个shecule不是serializable schedule。

考虑连续的两条指令I和J,如果I和J操作不同的数据项那么,这两个指令可以交换顺序,不会影响schedule的执行结果。如果I和J操作相同的数据项,那么只有当I和J都是read(Q)时才不会影响schedule的结果。如果两条连续的指令,操作相同的数据项,其中至少一个指令是write,那么I和J是conflict的。

如果schedule S连续的条指令I和J不conflict,我们可以交换它们执行的顺序,从而产生一个新的schedlue S',我们称S和S'conflict equivalent。如果S经过一系列conflict equivalent变换,和某个serializable schedule等价,那么我们称S是conflict serializable

比如下面这个schedule S:

T1 T2
read(A);
write(A);
read(A);
write(A);
read(B);
write(B);
read(B);
write(B);

经过多次conflict equivalent变换,生成新的schedule S',S'是serializable schedule。

T1 T2
read(A);
write(A);
read(B);
write(B);
read(A);
write(A);
read(B);
write(B);

所以S是conflict serializable的。


two-phase locking

不对加解锁进行限制

前面提到多个事务并发执行的时候,可能出现数据不一致得情况。一个很显然的想法是加锁来进行并发控制。

可以使用共享锁(lock-S),排他锁(lock-X)。

问题来了。

在什么时候加锁?什么时候释放锁?

考虑下面这种加解锁顺序:

事务一从账户B向账户A转移50。

T1:
lock-X(B);
read(B);
B := B - 50;
write(B);
unlock(B);
lock-X(A);
read(A);
A := A + 50;
write(A);
unlock(A).

事务二展示账户A和B的总和。

T2:
lock-S(A);
read(A);
unlock(A);
lock-S(B);
read(B);
unlock(B);
display(A+B).

可能出现这样一种schedule:

T1 T2
lock-X(B);
read(B);
B := B - 50;
write(B);
unlock(B);
lock-S(A);
read(A);
unlock(A);
lock-S(B);
read(B);
unlock(B);
display(A+B).
lock-X(A);
read(A);
A := A + 50;
write(A);
unlock(A).

假设初始时A和B分别是100和200,执行后事务二显示A+B为250,显然出现了数据不一致。

我们已经加了锁,为什么还会出现数据不一致?

问题出在T1过早unlock(B)。


two-phase locking

这时引入了two-phase locking协议,该协议限制了加解锁的顺序。

该协议将事务分成两个阶段,

Growing phase:事务可以获取锁,但是不能释放任何锁。

Shringking phase:事务可以释放锁,但是不能获取锁。

最开始事务处于Growing phase,可以随意获取锁,一旦事务释放了锁,该事务进入Shringking phase,之后就不能再获取锁。

按照two-phase locking协议重写之前的转账事务:

事务一从账户B向账户A转移50。

T1:
lock-X(B);
read(B);
B := B - 50;
write(B);
lock-X(A);
read(A);
A := A + 50;
write(A);
unlock(B);
unlock(A).

事务二展示账户A和B的总和。

T2:
lock-S(A);
read(A);
lock-S(B);
read(B);
display(A+B).
unlock(A);
unlock(B);

现在无论如何都不会出现数据不一致的情况了。


two-phase locking正确性证明

课本的课后题15.1也要求我们证明two-phase locking(以下称2PL rule)的正确性。我看了下解答,用的是反正法。我还看到一个用归纳法证的,比较有趣。

前提:



  1. 假设T1, T2, ... Tn,n个事务遵循two-phase locking协议。

  2. Sn是T1, T2, ... Tn并发执行的一个schdule。

目标:

证明Sn是conflict serializable的schedule。

证明开始:

起始步骤,n = 1的情况

T1遵守2PL rule。

S1这个schedule只包含T1。

显然S1是conflict serializable的schedule。

迭代步骤

迭代假设:假设Sn-1是T1, T2, ... Tn−1形成的一个schedule,并且Sn-1是conflict serializable的schedule。我们需要证明Sn-1是conflict serializable的schedule,Sn也是conflict serializable的schedule。

假设Ui(•)是事务i的解锁操作,并且是schedule Sn中第一个解锁的操作:

lab3_1_proof.PNG

可以证明,我们可以将事务i所有ri(•) and wi(•)操作移到Sn的最前面,而不会引起conflict。

证明如下:

令Wi(Y)是事务i的任意操作,Wj(Y)是事务j的一个操作,并且和Wi(Y)conflict。等价于证明不会出现如下这种情况:

lab3_2_proof.PNG

假设出现了这种情况,那么必然有如下加解锁顺序:

lab3_3_proof.PNG

又因为所有事务都遵守2PL rule,所以必然有如下加解锁顺序:

lab3_4_proof.PNG

冲突出现了,Ui(•)应该是Sn中第一个解锁操作,但是现在却是Uj(Y)。所以假设不成立,所以结论:"我们可以将事务i所有ri(•) and wi(•)操作移到Sn的最前面,而不会引起conflict"成立。

我们将事务i的所有操作移到schedule最前面,

lab3_5_proof.PNG

又因为Sn-1是conflict serializable的所以Sn是conflict serializable的。

证明完毕


two-phase locking不能保证不会死锁

two-phase locking可以保证conflict serializable,但可能会出现死锁的情况。

考虑这个schedule片段:

T1 T2
lock-X(B);
read(B);
B := B - 50;
write(B);
lock-S(A);
read(A);
lock-S(B);
lock-X(A);

T1和T2都遵循2PL rule,但是T2等待T1释放B上的锁,T1等待T2释放A上的锁,造成死锁。


死锁处理

有两类基本思路:



  1. 死锁预防,这类方法在死锁出现前就能发现可能导致死锁的操作。

  2. 死锁检测,这类方法定期执行死锁检测算法,看是否发生死锁,如果发生了,执行死锁恢复算法。

这里介绍wait-die这种死锁预防机制,该机制描述如下:

事务Ti请求某个数据项,该数据项已经被事务Tj获取了锁,Ti允许等待当且仅当Ti的时间戳小于Tj,否则Ti将被roll back。


wait-die正确性证明

为什么该机制能保证,不会出现死锁的情况呢?

如果Ti等待Tj释放锁,我们记Ti->Tj。那么系统中所有的事务将组成一个称作wait-for graph的有向图。容易证明:wait-for graph出现环和系统将出现死锁等价。

wait-die这种机制就能防止出现wait-for graph出现环。为什么?因为wait-die机制只允许时间戳小的等待时间戳大的事务,也就是说在wait-for graph中任意一条边Ti->Tj,Ti的时间戳都小于Tj,显然不可能出现环。所以不会出现环,也就不可能出现死锁。

LockManager的具体代码可以参考我的手实现:https://github.com/gatsbyd/cmu_15445_2018

参考资料:



  1. http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/7-serializability/2PL.html

  2. 《Database System concepts》 chapter 14, 15



推荐阅读
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • 本文档介绍了如何在Visual Studio 2010环境下,利用C#语言连接SQL Server 2008数据库,并实现基本的数据操作,如增删改查等功能。通过构建一个面向对象的数据库工具类,简化了数据库操作流程。 ... [详细]
  • 深入理解 JMeter 定时器
    本文详细介绍了JMeter中定时器的功能和使用方法,探讨了其在性能测试中的重要性,并结合实际案例解释了如何合理配置定时器以模拟真实的用户行为。文章还涵盖了定时器的执行顺序及其与其他元件的相互作用。 ... [详细]
  • KMP算法是处理字符串匹配的一种高效算法它首先用O(m)的时间对模板进行预处理,然后用O(n)的时间完成匹配。从渐进的意义上说,这样时间复 ... [详细]
  • 本文详细介绍如何通过设置SSH密钥来获取连接GitHub远程仓库的权限,包括生成密钥、添加到GitHub账户以及验证连接等步骤。 ... [详细]
  • 本文探讨了2019年前端技术的发展趋势,包括工具化、配置化和泛前端化等方面,并提供了详细的学习路线和职业规划建议。 ... [详细]
  • HTML基础入门指南
    本文将深入浅出地介绍HTML的基础知识,包括其定义、开发工具、制定机构、特性、基本标签及更多实用内容。 ... [详细]
  • Python 工具推荐 | PyHubWeekly 第二十一期:提升命令行体验的五大工具
    本期 PyHubWeekly 为大家精选了 GitHub 上五个优秀的 Python 工具,涵盖金融数据可视化、终端美化、国际化支持、图像增强和远程 Shell 环境配置。欢迎关注并参与项目。 ... [详细]
  • 深入解析ESFramework中的AgileTcp组件
    本文详细介绍了ESFramework框架中AgileTcp组件的设计与实现。AgileTcp是ESFramework提供的ITcp接口的高效实现,旨在优化TCP通信的性能和结构清晰度。 ... [详细]
  • 本文介绍了一种基于选择排序思想的高效排序方法——堆排序。通过使用堆数据结构,堆排序能够在每次查找最大元素时显著提高效率。文章详细描述了堆排序的工作原理,并提供了完整的C语言代码实现。 ... [详细]
  • 在项目中使用 Redis 时,了解其不同架构模式(如单节点、主从复制、哨兵模式和集群)对于确保系统的高可用性和扩展性至关重要。本文将详细探讨这些模式的特点和应用场景。 ... [详细]
  • CentOS 7.6环境下Prometheus与Grafana的集成部署指南
    本文旨在提供一套详细的步骤,指导读者如何在CentOS 7.6操作系统上成功安装和配置Prometheus 2.17.1及Grafana 6.7.2-1,实现高效的数据监控与可视化。 ... [详细]
  • 深入浅出TensorFlow数据读写机制
    本文详细介绍TensorFlow中的数据读写操作,包括TFRecord文件的创建与读取,以及数据集(dataset)的相关概念和使用方法。 ... [详细]
  • cJinja:C++编写的轻量级HTML模板引擎
    本文介绍了cJinja,这是一个用C++编写的轻量级HTML模板解析库。它利用ejson来处理模板中的数据替换(即上下文),其语法与Django Jinja非常相似,功能强大且易于学习。 ... [详细]
  • 深入解析:OpenShift Origin环境下的Kubernetes Spark Operator
    本文探讨了如何在OpenShift Origin平台上利用Kubernetes Spark Operator来管理和部署Apache Spark集群与应用。作为Radanalytics.io项目的一部分,这一开源工具为大数据处理提供了强大的支持。 ... [详细]
author-avatar
手机用户2502939977
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有