在关系数据库模型中,什么是关系的原子性

在关系数据库模型中,什么是关系的原子性,第1张

在关系数据模型中,关系可以看成由行和列交叉组成的二维表格,表中一行称为一个元组,可以用来标识实体集中的一个实体。表中的列称为属性,给每一列起一个名称即为属性名,表中的属性名不能相同。

列的取值范围称为域,同列具有相同的域,不同的列也可以有相同的域。表中任意两行(元组)不能相同。能唯一标识表中不同行的属性或属性组称为主键。

尽管关系与传统的二维表格数据文件具有类似之处,但是它们又有区别,严格地说,关系是一种规范化的二维表格!

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

我特别关注了@我练功发自真心

的答案,他正确地指出了,为了保证事务 *** 作的原子性,必须实现基于日志的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这样的开源引擎的源代码来读。不过,即使是一个非常粗糙的开源实现(不考虑太复杂的并行处理,不考虑分布式系统,不考虑针对 *** 作系统和硬件的优化之类),要基本搞明白恐怕也不是一两年的事。

在工作中,经常会接触到事务这个概念。涉及到事务,大家首先想到的就是事务的四个特性:ACID。

1.原子性(Atomicity)

1.1什么是原子性

一般来说,原子是指不能分解成小部分的东西。这个词在计算的不同分支中意味着相似但又微妙不同的东西。例如,在多线程编程中,如果一个线程执行一个原子 *** 作,这意味着另一个线程无法看到该 *** 作的一半结果。系统只能处于 *** 作之前或 *** 作之后的状态,而不是介于两者之间的状态。 ACID原子性的定义特征是:能够在错误时中止事务,丢弃该事务进行的所有写入变更的能力。

1.2 如何实现原子性

WAL(预写日志) 是用于保证事务的原子性和持久性。简单来讲,事务更新数据之前,先写日志,然后在更新数据。当系统崩溃时,如果事务还没写WAL,那整个数据依然一致。如果事务只写了WAL,未更新具体的数据页后崩溃,那恢复流程可以根据WAL日志,重做相关 *** 作,保证数据一致性。

2.一致性(Consistency)

ACID一致性的概念是,对数据的一组特定陈述必须始终成立。即不变量(invariants)。例如,在会计系统中,所有账户整体上必须借贷相抵。如果一个事务开始于一个满足这些不变量的有效数据库,且在事务处理期间的任何写入 *** 作都保持这种有效性,那么可以确定,不变量总是满足的。 原子性,隔离性和持久性是数据库的属性,而一致性(在ACID意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。

3.隔离性(Isolation)

3.1什么是隔离性

大多数数据库都会同时被多个客户端访问。如果它们各自读写数据库的不同部分,这是没有问题的,但是如果它们访问相同的数据库记录,则可能会遇到并发问题(竞争条件(race conditions))。 ACID意义上的隔离性意味着,同时执行的事务是相互隔离的:它们不能相互冒犯。

如果两个事务不触及相同的数据,它们可以安全地并行(parallel) 运行,因为两者都不依赖于另一个。当一个事务读取由另一个事务同时修改的数据时,或者当两个事务试图同时修改相同的数据时,并发问题(竞争条件)才会出现。出于这个原因,数据库一直试图通过提供事务隔离(transaction isolation) 来隐藏应用程序开发者的并发问题。

serializable级别的隔离,保证事务的效果与连续运行(即一次一个,没有任何并发)是一样的,可以保证事务地安全执行。但是在serializable隔离级别,事务并发度很低,整个数据库的性能肯定不高。这时候,数据库开发人员有提出了四种不同的隔离级别,来平衡事务并发度与隔离性,这四个隔离级别分别是:

读未提交(Read Uncommitted):可以读取未提交的记录。

读已提交(Read Committed):事务中只能看到已提交的修改。

可重复读(Repeatable Read):解决了不可重复读问题(MySQL 默认隔离级别)

序列化(Serializable):最高隔离级别。

RU,RC和RR由于降低了隔离要求,自然在读取数据时,会产生各种异常(上帝为你打开一扇门的同时,肯定也为你关上一扇窗):

RU会读取其他事务未提交的数据,这就产生了脏读,脏读取意味着另一个事务可能会只看到一部分更新,或者看到的数据已经被回滚了。

RC级别的隔离,会产生不可重复读的问题。所谓不可重复读是指在一个事务内根据同一个条件对行记录进行多次查询,但是搜出来的结果却不一致。发生不可重复读的原因是在多次搜索期间查询条件覆盖的数据被其他事务修改了。

RR级别的隔离,会产生幻读问题。幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select *** 作得到的结果所表征的数据状态无法支撑后续的业务 *** 作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。

3.2 如何支持隔离性

一般数据库不会考虑工作在RU隔离级别,因为读脏数据会引起太多的问题。数据库一般工作在RC或RR隔离级别,快照隔离级别就可以支持RC或RR隔离级别,所以数据库一般会实现快照隔离级别。

快照隔离的实现通常使用写锁来防止脏写,这意味着进行写入的事务会阻止另一个事务修改同一个对象。但是读取不需要任何锁定。从性能的角度来看,快照隔离的一个关键原则是:读不阻塞写,写不阻塞读。这允许数据库在处理一致性快照上的长时间查询时,可以正常地同时处理写入 *** 作。且两者间没有任何锁定争用。

为了实现快照隔离,数据库必须保留一个对象的几个不同的提交版本,因为各种正在进行的事务可能需要看到数据库在不同的时间点的状态。因为它并排维护着多个版本的对象,所以这种技术被称为多版本并发控制(MVCC, multi-version concurrentcy control)。

最高的隔离级别:Serializable,一般是通过2PL来实现, 一阶段申请,一阶段释放。读写都要加锁。

4.持久性(Durability)

数据库系统的目的是,提供一个安全的地方存储数据,而不用担心丢失。持久性 是一个承诺,即一旦事务成功完成,即使发生硬件故障或数据库崩溃,写入的任何数据也不会丢失。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9628149.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-30
下一篇 2023-04-30

发表评论

登录后才能评论

评论列表(0条)

保存