和数据库打交道的程序员绕不开的话题就是:事务,作为一个简化访问数据库的应用程序的编程模型。通过使用事务,应用程序可以忽略某些潜在的错误场景和并发问题,由数据库负责处理它们。而并非每个应用程序都需要事务,有时削弱事务性担保或完全放弃事务,可以获得更高的性能或更高的可用性。怎么样更好的理解数据库中的事务与隔离级别呢?我们借这篇文章来聊一聊吧~

1.ACID

1983年,Andreas Reuter and Theo Härder 提出了事务之中重要的四个特性:

  • 原子性(Atomicity)
    一般来说,原子指的是不能分解成更小的部分的东西。如果写操作被组合到一个原子事务中,并且由于一个错误,事务不能完成,那么事务将被中止,数据库必须丢弃或撤消它在该事务中所做的任何写入操作。原子性简化了数据库的数据模型:如果一个事务被中止时,应用程序可以确保它没有任何改变,因此可以被重试。

  • 一致性(Consistency)
    一致性的表述是:数据库之中的数据必须始终正确。例如,在一个会计系统,所有账户的收支必须平衡。应用程序有责任正确定义其事务,从而保持一致性。这不是数据库能保证的:如果你写了违反你的不变量的坏数据,数据库不能阻止你。应用程序可能会依赖于数据库的原子性和隔离性以达到一致性。

  • 隔离性(Isolation):
    数据库由多个客户端同时访问时,如果他们访问相同的数据库记录,你会遇到并发问题。如下图所示:

    隔离性意味着并发执行的事务彼此隔离,数据库确保当事务提交时,结果与它们顺序运行相同,即使它们实际上是并发运行的。

  • 持久性(Durability):
    持久性是一个承诺,一旦事务成功提交,它所写的任何数据将不会丢失,即使有硬件故障或数据库崩溃。在单节点数据库中,持久性通常意味着数据已写入非易失性存储(如硬盘驱动器或SSD)。它通常还需要写入日志,以便出现文件损坏时恢复工作。在分布式数据库中,持久性可能意味着数据已成功复制到一些节点上。

在几种特性之中,隔离性是DBA对数据库调优最为侧重的部分,接下来,我们着重来聊一聊事务的隔离性。

2. 隔离级别

如果两个事务不触及相同的数据,它们可以安全地并行运行,因为两者都不依赖于其他数据。当一个事务读取另一个事务同时修改的数据,或者两个事务试图同时修改同一数据时,便会出现并发问题。

并发错误很难通过测试发现,因为这种的错误触发具有偶然性,通常很难重现。并发性也很难推理,尤其是在大型应用程序中,因为开发人员不一定知道其他代码片段正在访问数据库。所以数据库通过提供事务的隔离性来隐藏应用程序开发者的并发问题,屏蔽了底层数据库的并发细节,提供了一个串行化的数据模型。

天下没有免费的午餐,串行化的隔离级别会带来额外的性能开销,所以许多数据库会提供一些弱隔离级别作为选择,它们可以防止一部分并发问题。所以,接下来,我们将一一梳理,不同的隔离级别之间的差异。

Read Committed

最基本的隔离级别是Read Committed

  • 当从数据库中读取数据时,只看到已提交的数据(没有脏读)。
  • 当写入数据库时,只覆盖已提交的数据(没有脏写)。
脏读:

一个事务已经向数据库写入了一些数据,但该事务尚未提交或中止。另一个事务可以看到未提交的数据,就称为脏读Read Committed的隔离级别可以防止脏读。所以当事务提交之后,事务中的写操作才对其他人可见。如下图所示:

脏写:

写操作覆盖了一个未提交的值,被称之为脏写Read Committed的隔离级别事务可以防止脏写,通常是通过延迟写操作直到前一个写事务已提交或中止时在继续写入。脏写会导致数据出现不一致,如下图所示:Alice和Bob要买同一个东西,脏写导致了最终的买家是Bob,而发票却寄给了Alice。

实现:

Read Committed是一种十分流行的隔离级别,许多数据库的默认隔离级别便是Read Committed。

数据库通过使用行级锁防止脏写:当事务要修改某个特定行时,它必须首先获取该行的锁。然后必须保留该锁,直到事务提交或中止为止。只有一个事务可以锁定任何给定行的锁;如果另一个事务要写入同一个行,则必须等到第一个事务提交或中止后才可获取锁并继续。

而使用行级锁避免脏读会产生很大的代价,容易找出读延迟。使用当事务正在进行时,读取同一行的任何其他事务都只给出旧值。只有当新值被提交时,事务才切换到读取新值。

Read Repeatable

Read Committed看起来是一个很好的隔离级别了,但是它也会产生一些问题,我们看下面这个例子:如图所示,Alice在一家银行有1000美元的存款,在两个账户上拆分,每个账户有500美元。现在,一个事务从她的帐户转到另一个帐户100美元。如果她很不幸地在事务正在进行的同一时刻查看她的账户余额清单,她可能会看到一个账户余额在收到的款项到达之前(余额为500美元),另一个账户在已进行的转移之后(新余额为400美元),而100美元消失了。

在Read Committed隔离级别之下出现的这种异常被称为不可重复读,我们需要寻找新的解决方案。

快照隔离

为了实现可重复读,我们需要快照隔离的技术。

每个事务都从数据库的快照中读取的,即事务在事务开始时看到数据库中提交的所有数据。即使数据随后被另一个事务更改,每个事务只看到来自特定时间点的旧数据。当事务可以看到数据库的数据,在特定时间点被冻结了。

快照隔离的实现通常使用写锁来防止脏写,这意味着编写的事务可以阻止写入同一对象的另一个事务的进程。实现快照隔离,数据库必须保留数据的几个不同的提交版本,因为各种正在进行的事务可能需要在不同的时间点查看数据库的状态,这种技术被称为多版本并发控制(MVCC)

如下图所示,每当一个事务向数据库写入任何内容时,它写入的数据都会用事务ID进行标记。

当事务从数据库中读取时,事务ID用于决定哪些数据可见,哪些数据是不可见的。在每次更改值时创建新版本,数据库可以提供快照隔离,而只产生较小的开销。

Serializability

Read Repeatable虽然解决了读取数据的问题,但是依然没有办法解决并发写的问题。我们来看看下面这个例子:医院通常在任何时候都要有几个值班医生,必须至少有一位医生在值班。医生可以调整他们的轮班,前提是至少有一个同事在医院值班。Alice和Bob是两位今天值班的医生。两人都想调整轮班,不幸的是,他们碰巧点击按钮大约在同一时间取消轮班。接下来发生的情况如图所示:

由于数据库的隔离级别是快照隔离,两个人都检查到目前有两个人值班,因此两个事务都进入下一个阶段。Alice认为请假没有问题,Bob也认为请假没有问题。两个事务都提交了,现在没有医生在值班了,数据库的一致性出现了问题。

Serializability 被看作是最强的隔离级别。数据库保证,如果事务在单独运行时行为正确,则它们在并发运行时仍然正确,换句话说,数据库防止所有可能的竞争条件。接下来我们将详细来聊一聊Serializability的隔离级别是如何实现的。

两阶段锁(2PL)

数据库发展几十年来,广泛使用的算法:两阶段锁(2PL)

  • 事务A获取了数据的读锁,而事务B想写对应的数据,则必须事务A提交或中止后方可继续写入操作。这可以确保事务B不会意外地改变事务A正在读取的数据。
  • 事务A获取了数据的写锁,事务B想读取对应的数据,事务B也必须等到事务A提交或中止后方可进行读取。
  • 事务A获取了数据的写锁,事务B想写对应的数据,事务B也必须等到事务A提交或中止后方可进行写入操作。

由上面三个规则可以看出,2PL提供串行化的访问,它可以防止任何的并发问题,但是由此带来的问题也显而易见,数据库的并发能力大大降低了。

共享锁与独占锁

两阶段锁的逻辑是通过共享锁与独占锁共同来实现的:
如果事务A要读取数据,则必须先获取共享锁。数据库允许多个事务同时拥有共享锁,但如果另一个事务拥有独占锁,则其他事务要获取共享锁则必须等待。

如果事务A要写入数据,则必须先获取独占锁。任何其他事务都不能同时拥有锁,(无论是共享还是独占)因此如果对象上存在任何锁,事务A必须等待。

如果事务A先读取数据,然后写入数据。它可以将共享锁升级为独占锁。升级与直接获得独占锁相同。

在事务获得锁之后,它必须继续持有锁直到事务结束(提交或中止)。这就是“两阶段”的名称:第一阶段在获取锁时,第二阶段释放锁。

由于使用了这么多锁,所以很容易发生事务A被卡住等待事务B释放它的锁,反之亦然。这种情况称为死锁。数据库自动检测死锁之后会终止事务,然后重启事务排队。

序列化的快照隔离(SSI)

两阶段锁(2PL)由于采取了悲观的并发控制,不但容易引起死锁,且性能低下。所以接下来我们要来看看序列化的快照隔离(SSI),它提供了完整的串行化,但是只有很小的性能损失相比两阶段锁。

当我们以前讨论快照隔离中的并发写问题,是因为事务从数据库读取一些数据,检查读取结果,并决定根据它看到的结果采取一些操作。然而,在快照隔离的情况下,原始查询的结果在事务提交时可能不再是最新的,因为数据可能在此期间进行了修改。所以查询和事务中的写之间可能存在因果依赖关系。为了提供串行化隔离,数据库可以检测到这种情况,并且终止不合法的事务。

检测是否读取旧的数据

快照隔离通常采用多版本并发控制实现,当一个事务读取一个数据库的一致性快照,它忽略了新的写入。为了防止这种异常,数据库需要跟踪事务时读取时是否忽略了另一个事务的写操作,当事务要提交时,数据库检查任何已忽略的写操作。如果忽略了写操作,则必须中止事务。

为什么要等到提交时,而不是检测到读取旧数据时就立即终止事务呢?那么,如果事务如果是只读事务,则不需要中止,在事务进行读取时,数据库还不知道该事务是否稍后将执行写入操作。上文Alice与Bob请假的例子可以通过这样的方式避免并发写的问题:

检测影响先前读取的写入

如果并没有检测到读取了旧的数据,仍然有可能出现并发写入的问题。

所以当事务写入数据库时,它记录读取受影响数据的任何其他事务的索引。一旦第一个事务是成功提交,其他所有相关的索引事务必须终止。通过这样快照隔离的方式,保证了并发写入的安全性。同样是上文的例子,下图暂时了索引终止技术:

许多工程细节影响算法在实践中的工作效果。跟踪事务的读写的粒度。如果数据库非常详细地跟踪每一个事务的活动,那么它就可以精确地判断哪些事务需要中止,但是这些开销会变得很大。而不太详细的跟踪事务会更快速,但可能导致更多的事务被中止。相比与两阶段锁,可串行化隔离快照是大有好处的:一个事务不需要阻塞等待另一个事务持有的锁。

小结:

我们在本篇之中总结了数据库事务与隔离运用到的多种策略与技术,希望大家能够更好的认识事务在数据库系统之中的重要意义,并且能够为自己的开发环境运用最恰当的隔离级别。

转载于:https://www.cnblogs.com/happenlee/p/8447134.html

事务与隔离级别------《Designing Data-Intensive Applications》读书笔记10相关推荐

  1. 数据库事务的隔离级别 (转)

    .NET 提供的数据库事务隔离级别 System.Data.IsolationLevel 枚举用于指定连接的事务锁定行为,它包含如下枚举:   成员名称 说明 Chaos 无法改写隔离级别更高的事务中 ...

  2. 数据库事务的隔离级别

    .NET 提供的数据库事务隔离级别 System.Data.IsolationLevel 枚举用于指定连接的事务锁定行为,它包含如下枚举: Chaos 无法改写隔离级别更高的事务中的挂起的更改.  R ...

  3. 事物的级别_实战分析:事务的隔离级别和传播属性

    什么是事务? 要么全部都要执行,要么就都不执行. 事务所具有的四种特性 原子性,一致性,隔离性,持久性 原子性 个人理解,就是事务执行不可分割,要么全部完成,要么全部拉倒不干. 一致性 关于一致性这个 ...

  4. mysql重复读导致余额不对_我所理解的MySQL之四:事务、隔离级别及MVCC

    mysql教程栏目介绍MySQL相关的事务.隔离级别及MVCC. MySQL 系列的第四篇,主要内容是事务,包括事务 ACID 特性,隔离级别,脏读.不可重复读.幻读的理解以及多版本并发控制(MVCC ...

  5. spring事务的隔离级别和传播特性详解(附实例)

    spring支持编程式事务管理和声明式事务管理两种方式. 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager.对于编程式事务 ...

  6. 事务和事务的隔离级别及脏读、幻读

    1.为什么需要事务 事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位(不可再进行分割),由一个有限的数据库操作序列构成(多个DML语句,select语句不包含事务),要不全部成功,要不全部不成 ...

  7. 事务和事务的隔离级别

    MySQL学习系列 为什么需要事务 事务是数据库管理系统(DBMS) 执行过程中的一个逻辑单位(不可再进行分割) , 由一个有限的数据库操作序列构成(多个 DML 语句, select 语句不包含 事 ...

  8. 数据库并发机制和事务的隔离级别详解

    (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/64444896冷血之心的博客) 本文将从以下4个方面来展开: (1) ...

  9. mysql数据 锁 隔离级别_MySQL数据库事务各隔离级别加锁情况--read uncommitted篇

    1.目的 1.1 合适人群 1.数据库事务特征我只是背过,并没有很深刻的理解. 2.数据库事务的隔离级别只是了解,并没有深刻理解,也没有在实际工作中体验使用过. 3.经常面试被人问起数据库加锁情况,一 ...

最新文章

  1. Android内存管理之道
  2. 6个快速优化回归测试套件的方法,你都知道吗?
  3. 全球及中国彩超市场销售渠道与投资竞争力研究报告2022版
  4. 踩坑日记:Logstash同步数据库有type字段导致同步失败
  5. 直方图尖峰python_Python系统学习 - 绘制直方图
  6. 文件已经上传到服务器翻译,服务器接受上传的优化 翻译+源码分析
  7. Python3 爬虫实战 — 豆瓣电影TOP250【requests、Xpath、正则表达式、CSV、二进制数据储存】
  8. dockerfile 修改文件权限_网易技术实践|Docker文件系统实战
  9. UVa 1153 Keep the Customer Satisfied 【贪心 优先队列】
  10. jquery的全选代码
  11. Request 获取网址各片段
  12. Linux VNC使用
  13. 计算机审计取证方法,审计技术方法有几种
  14. 【JavaScript】新浪微博批量删除脚本
  15. 【毕设资料】 Web版RSS阅读器(一)——dom4j读取xml(opml)文件
  16. 35岁后你的职场危机,你该何去何从
  17. ESP8266 MP3制作——esp8266联网
  18. java生成树形Excel_poi从excel中读取父子关系型(树形)数据结构到数据库
  19. python not in函数用法,pandas is in和not in的使用说明
  20. MyCms 自媒体 CMS 系统 v3.1.0,新增商城接口

热门文章

  1. 微信小程序开发(十二)富文本插件wxParse的使用
  2. mysql双一参数_mysql的双1设置
  3. QNX Neutrino 微内核
  4. 便利店新零售怎么做,才能实现销售额倍增?
  5. 大创项目部分笔记(1)
  6. PCB生产企业自动化立体仓库/智能仓库库系统WMS/WCS解决方案
  7. STM32官方的一些参考手册资料
  8. 计算机动画电影英语翻译,英语翻译_推荐!最适合练口语的10部动画电影_沪江英语...
  9. ffmpeg 录制屏幕
  10. 野火A7学习第十次(状态机相关)