数据库事务原子性、一致性是怎样实现的?[转]

这个问题的有趣之处,不在于问题本身(“原子性、一致性的实现机制是什么”),而在于回答者的分歧反映出来的另外一个问题:原子性和一致性之间的关系是什么?

我特别关注了@我练功发自真心 的答案,他正确地指出了,为了保证事务操作的原子性,必须实现基于日志的REDO/UNDO机制。但这个答案仍然是不完整的,因为原子性并不能够完全保证一致性

按照我个人的理解,在事务处理的ACID属性中,一致性是最基本的属性,其它的三个属性都为了保证一致性而存在的。

首先回顾一下一致性的定义。所谓一致性,指的是数据处于一种有意义的状态,这种状态是语义上的而不是语法上的。最常见的例子是转帐。例如从帐户A转一笔钱到帐户B上,如果帐户A上的钱减少了,而帐户B上的钱却没有增加,那么我们认为此时数据处于不一致的状态。

在数据库实现的场景中,一致性可以分为数据库外部的一致性和数据库内部的一致性。前者由外部应用的编码来保证,即某个应用在执行转帐的数据库操作时,必须在同一个事务内部调用对帐户A和帐户B的操作。如果在这个层次出现错误,这不是数据库本身能够解决的,也不属于我们需要讨论的范围。后者由数据库来保证,即在同一个事务内部的一组操作必须全部执行成功(或者全部失败)。这就是事务处理的原子性。

为了实现原子性,需要通过日志:将所有对数据的更新操作都写入日志,如果一个事务中的一部分操作已经成功,但以后的操作,由于断电/系统崩溃/其它的软硬件错误而无法继续,则通过回溯日志,将已经执行成功的操作撤销,从而达到“全部操作失败”的目的。最常见的场景是,数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程:读取日志进行REDO(重演将所有已经执行成功但尚未写入到磁盘的操作,保证持久性),再对所有到崩溃时尚未成功提交的事务进行UNDO(撤销所有执行了一部分但尚未提交的操作,保证原子性)。crash recovery结束后,数据库恢复到一致性状态,可以继续被使用。

日志的管理和重演是数据库实现中最复杂的部分之一。如果涉及到并行处理和分布式系统(日志的复制和重演是数据库高可用性的基础),会比上述场景还要复杂得多。

但是,原子性并不能完全保证一致性。在多个事务并行进行的情况下,即使保证了每一个事务的原子性,仍然可能导致数据不一致的结果。例如,事务1需要将100元转入帐号A:先读取帐号A的值,然后在这个值上加上100。但是,在这两个操作之间,另一个事务2修改了帐号A的值,为它增加了100元。那么最后的结果应该是A增加了200元。但事实上, 事务1最终完成后,帐号A只增加了100元,因为事务2的修改结果被事务1覆盖掉了。

为了保证并发情况下的一致性,引入了隔离性,即保证每一个事务能够看到的数据总是一致的,就好象其它并发事务并不存在一样。用术语来说,就是多个事务并发执行后的状态,和它们串行执行后的状态是等价的。怎样实现隔离性,已经有很多人回答过了,原则上无非是两种类型的锁:

一种是悲观锁,即当前事务将所有涉及操作的对象加锁,操作完成后释放给其它对象使用。为了尽可能提高性能,发明了各种粒度(数据库级/表级/行级……)/各种性质(共享锁/排他锁/共享意向锁/排他意向锁/共享排他意向锁……)的锁。为了解决死锁问题,又发明了两阶段锁协议/死锁检测等一系列的技术。

一种是乐观锁,即不同的事务可以同时看到同一对象(一般是数据行)的不同历史版本。如果有两个事务同时修改了同一数据行,那么在较晚的事务提交时进行冲突检测。实现也有两种,一种是通过日志UNDO的方式来获取数据行的历史版本,一种是简单地在内存中保存同一数据行的多个历史版本,通过时间戳来区分。

锁也是数据库实现中最复杂的部分之一。同样,如果涉及到分布式系统(分布式锁和两阶段提交是分布式事务的基础),会比上述场景还要复杂得多。

@我练功发自真心 提到,其他回答者说的其实是操作系统对atomic的理解,即并发控制。我不能完全同意这一点。数据库有自己的并发控制和锁问题,虽然在原理上和操作系统中的概念非常类似,但是并不是同一个层次上的东西。数据库中的锁,在粒度/类型/实现方式上和操作系统中的锁都完全不同。操作系统中的锁,在数据库实现中称为latch(一般译为闩)。其他回答者回答的其实是“在并行事务处理的情况下怎样保证数据的一致性”。

最后回到原来的问题(“原子性、一致性的实现机制是什么”)。我手头有本Database System Concepts(4ed,有点老了),在第15章的开头简明地介绍了ACID的概念及其关系。如果你想从概念上了解其实现,把这本书的相关章节读完应该能大概明白。如果你想从实践上了解其实现,可以找innodb这样的开源引擎的源代码来读。不过,即使是一个非常粗糙的开源实现(不考虑太复杂的并行处理,不考虑分布式系统,不考虑针对操作系统和硬件的优化之类),要基本搞明白恐怕也不是一两年的事。

编辑于 2015-11-16

1639 条评论

分享

收藏感谢收起

沈杰

PHP开发工程师

53 人赞同了该回答

先借用前辈的一句话:数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别。

隔离性分为四个级别:
1读未提交:(Read Uncommitted)
2读已提交(Read Committed) 大多数数据库默认的隔离级别
3可重复读(Repeatable-Read) mysql数据库所默认的级别
4序列化(serializable)

四个级别的具体实现和不同的请下面细读:

首先程序是可以并发执行的,同样,在MySQL中,一个表可以由两个或多个进程同时来读写数据,这是没有问题的。

比如,此时有两个进程来读数据,这也没什么问题,允许。但是如果一个进程在读某一行的数据的过程中,另一个在进程又往这一行里面写数据(改、删),那结果会是如何?同样,如果两个进程都同时对某一行数据进行更改,以谁的更改为准?那结果又会怎样,不敢想象,是不是数据就被破坏掉了。所以此时是冲突的。

既然会冲突就要想办法解决,靠谁来解决,这时候就是靠锁机制来维护了。怎么使用锁来使他们不冲突?

在事务开始的时候可以给要准备写操作的这一行数据加一个排它锁,如果是读操作,就给该行数据一个读锁。这样之后,在修改该行数据的时候,不让其他进程对该行数据有任何操作。而读该行数据的时候,其他进程不能更改,但可以读。读或写完成时,释放锁,最后commit提交。这时候读写就分离开了,写和写也就分离开了。
注意:此时加锁和释放锁的过程由mysql数据库自身来维护,不需要我们人为干涉。mysql开发者给这个解决冲突的方案起了一个名字叫做:读未提交:(Read
Uncommitted)。这也就是事务的第一个隔离性。

但是这个程度的隔离性仅仅是不够的。看下面的测试结果:

1)A修改事务级别为:未提交读。并开始事务,对user表做一次查询

2)B事务更新一条记录

3)此时B事务还未提交,A在事务内做一次查询,发现查询结果已经改变

4)B进行事务回滚

5)A再做一次查询,查询结果又变回去了

由试验得知:在一个进程的事务当中,我更改了其中的一行数据,但是我修改完之后就释放了锁,这时候另一个进程读取了该数据,此时先前的事务是还未提交的,直到我回滚了数据,另一个进程读的数据就变成了无用的或者是错误的数据。我们通常把这种数据叫做脏数据,这种情况读出来的数据叫做賍读。

怎么办?依然是靠锁机制。无非是锁的位置不同而已,之前是只要操作完该数据就立马释放掉锁,现在是把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作。那么数据库为此种状态的数据库操作规则又给了一个名字叫做:读已提交(Read Committed),或者也可以叫不可重复读。这也就是事务的第二个隔离性。

在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……

继续看下面的测试结果:

1)把隔离性调为READ-COMMITTED(读取提交内容)设置A的事务隔离级别,并进入事务做一次查询

2)B开始事务,并对记录进行修改

3)A再对user表进行查询,发现记录没有受到影响

4)B提交事务

5)A再对user表查询,发现记录被修改

试验进行到这里,你会发现,在同一个事务中如果两次读取相同的数据时,最后的结果却不一致。这里我们把这种现象称为:不可重复读。因为在第一个事务读取了数据之后,此时另一个事务把该数据给修改了,这时候事务提交,那么另一个事务在第二次读取的时候,结果就不一样,一个修改前的,一个是修改后的。

但是细心的你会发现,既然你说此种隔离性是在事务提交后才释放锁,那么在试验过程中,在该数据未提交前,另一个事务为什么也是仍然可以读取的呀。是我说错了吗?不是的,在这里mysql使用了一个并发版本控制机制,他们把它叫做MVCC,通俗的也就是说:mysql为了提高系统的并发量,在事务未提交前,虽然事务内操作的数据是锁定状态,但是另一个事务仍然可以读取,大多数数据库默认的就是这个级别的隔离性。但mysql不是。

而且不只是在更新数据时出现这个问题,在插入数据时仍然会造成类似的这样一种现象:mysql虽然锁住了正在操作的数据行,但它仍然不会阻止另一个事务往表插入新行新的数据。比如:一个事务读取或更新了表里的所有行,接者又有另一个事务往该表里插入一个新行,在事务提交后。原来读取或更改过数据的事务又第二次读取了相同的数据,这时候这个事务中两次读取的结果集行数就不一样。原来更新了所有行,而现在读出来发现竟然还有一行没有更新。这就是所谓的幻读。

为了防止同事务中两次读取数据不一致,(包括不可重读和幻读),接下来该如何继续做呢?!

mysql依然采取的是MVCC并发版本控制来解决这个问题。具体是:如果事务中存在多次读取同样的数据,MySQL第一次读的时候仍然会保持选择读最新提交事务的数据,当第一次之后,之后再读时,mysql会取第一次读取的数据作为结果。这样就保证了同一个事务多次读取数据时数据的一致性。这时候,mysql把这种解决方案叫做:可重复度(Repeatable-Read),也就是上述所写的第三个隔离性,也是mysql默认的隔离级别。

注意:幻读和不可重复读(Read Committed)都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

说到这里,真的就完事了吗?到这里其实mysql并未完全解决数据的一致性问题。只是在读取上做了手脚,解决了传统意义上的幻读和不可重复读。
例子:1 A事务开启,B事务开启。
2 B事务往表里面插入了一条数据,但还并未提交。
3 A事务开始查询了,并没有发现B事务这次插入的数据。然后此时B事务提交了数据。
4 于是乎,A事务就以为没有这条数据,就开始添加这条数据,但是却发现,发生了数据 重复冲突。

最后这个时候,该我们的最后一种隔离级别也是最高的隔离级:别序列化(serializable)登场了。
该隔离级别会自动在锁住你要操作的整个表的数据,如果另一个进程事务想要操作表里的任何数据就需要等待获得锁的进程操作完成释放锁。可避免脏读、不可重复读、幻读的发生。当然性能会下降很多,会导致很多的进程相互排队竞争锁。

后记:以上所说的四种隔离性的锁机制应用是数据库自动完成的,不需要人为干预。隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效

https://www.zhihu.com/question/30272728

时间: 2024-11-07 06:31:42

数据库事务原子性、一致性是怎样实现的?[转]的相关文章

数据库事务的四大特性ACID

本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别. 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,所以事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响.为了实现原子性,需要通过日志:将所有对数据的更新操作都写入日志,如果一个事务中的一部分操作已经成功,但以后的操作,由于断电/系统崩溃/其它的软硬件错误而无法继续,则通过

数据库事务的ACID(原子性,一致性,隔离性,持久性)四大特性

我从维基百科翻译的.翻译水平有限: 在计算机科学中,ACID(原子性,一致性,隔离性,持久性)是数据库事务的一组属性,旨在即使在发生错误,断电等情况下也可以确保有效性.在数据库的上下文中,这是指一系列数据库操作,它们可以满足ACID属性(并且可以将这些属性视为对数据的单个逻辑操作)称为事务.例如,从一个银行帐户到另一个银行帐户的资金转移,即使涉及多个更改,例如从一个帐户借记而向另一个帐户贷记,也只是一次交易. 1983年, Andreas Reuter和TheoHärder在吉姆·格雷(Jim

数据库事务的四大特性和事务隔离级别

Reference: [1] http://www.cnblogs.com/fjdingsd/p/5273008.html [2] http://blog.csdn.net/fg2006/article/details/6937413 数据库事务四大特性 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全

数据库-事务和锁

事务 所谓事务是用户定义的一个数据库操作系列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位.例如在关系数据库中,一个事务可以是一条sql语句.一组sql语句或整个程序. 给个栗子: 小IT在网上购物,其付款过程至少包括以下几步数据库操作: 更新客户所购商品的库存信息: 生成订单并且保存到数据库: 更新用户相关信息,例如购物数量等: 正常情况下,操作顺利进行,最终交易成功,那么与交易相关的所有数据库信息也成功更新.但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存

[数据库事务与锁]详解三: 深入分析事务的隔离级别

注明: 本文转载自http://www.hollischuang.com/archives/943 本文详细介绍四种事务隔离级别,并通过举例的方式说明不同的级别能解决什么样的读现象.并且介绍了在关系型数据库中不同的隔离级别的实现原理. 在DBMS中,事务保证了一个操作序列可以全部都执行或者全部都不执行(原子性),从一个状态转变到另外一个状态(一致性).由于事务满足久性.所以一旦事务被提交之后,数据就能够被持久化下来,又因为事务是满足隔离性的,所以,当多个事务同时处理同一个数据的时候,多个事务直接

JDBC基础学习(四)—数据库事务

一.事务基本认识 1.事务的概述      为了保证数据库中数据的一致性,数据的操作应当是离散的成组的逻辑单元.当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应当全部视为错误,所有从起始点以后的操作应全部回退到开始状态.      事务的操作: 先定义开始一个事务,然后对数据做修改操作,这时如果提交(commit),这些数据就永久的保存下来,如果回退(rollback),数据库管理系统就放弃所有的修改而回到开始事务的状态.   2.事务的属性 (1)原子性(Atm

数据库事务解析

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源.通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠.一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性.一致性.隔离性和持久性)属性.事务是数据库运行中的一个逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理. 1.操

彻底理解数据库事务

事务 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit).在计算机术语中,事务通常就是指数据库事务. 概念 一个数据库事务通常包含对数据库进行读或写的一个操作序列.它的存在包含有以下两个目的: 1.为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法.2.当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰.

数据库的读一致性分析

前言 提起数据库的事务,我们就会想到ACID特性: A:Atomicity 原子性    事务中包含的各种操作,要么一起成功,要么全部失败 C:Consistency 一致性  事务从一个一致性的状态转变成另一个一致性的状态 I:Isolation 隔离性   各个事务之间的可见程度 D:Durability 持久性  数据库中的数据的改变应该是可以持久存储的 本篇博客将以MySQL为例分析数据库的读一致性.想分析清楚一致性,必先了解隔离级别. 数据库的隔离级别 在SQL标准中是定义了几种隔离级