设为首页 加入收藏

TOP

事务与隔离级别------《Designing Data-Intensive Applications》读书笔记10(一)
2019-09-17 18:55:05 】 浏览:39
Tags:事务 隔离 级别 ------ Designing Data-Intensive Applications 读书 笔记

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

1.ACID

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

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

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

  • 隔离性(Isolation):
    数据库由多个客户端同时访问时,如果他们访问相同的数据库记录,你会遇到并发问题。如下图所示:
    并发写对隔离性的破坏
    隔离性意味着并发执行的事务彼此隔离,数据库确保当事务提交时,结果与它们顺序运行相同,即使它们实际上是并发运行的。

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

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

2. 隔离级别

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

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

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

Read Committed

最基本的隔离级别是Read Committed

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

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

脏写:

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

实现:

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

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

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

Read Repeatable

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

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

快照隔离

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

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

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

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

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

Serializability

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

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

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

两阶段锁(2PL)

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

  • 事务A获取了数据的读锁,而事务B想写对应的数据,则必须事务A提交或中止后方可继续写入操作。这可以确保事务B不会意外
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇分布式系统的烦恼------《Designi.. 下一篇java抽象类,接口与异常

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目