数据库事务的ACID属性详解
数据库事务的ACID属性详解
事务是数据库管理系统(DBMS)中一个非常重要的概念,它确保了数据操作的可靠性和一致性。ACID属性是衡量事务质量的四个核心标准,分别代表原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。本文将深入探讨ACID属性的内涵及其在实际应用中的挑战。
为什么 ACID 很必要
ACID 的概念最初是由美国计算机科学家 James Gray 提出的(他有时也被叫做 Jim Gray),他在论文《The notions of consistency and predicate locks in a database system》中正式定义了数据库事务的概念和数据一致性的机制。后人则在其理论基础上提出了 ACID 理论,它是事务必须具有的 4 种特性(遗憾的是,我们不得不对“必须”持有怀疑的态度)。
(James Gray)
事务的概念是容易理解的,它是一个独立的且不可拆分的操作集合。这些操作要么全部成功执行,要么都不执行,否则就会出现数据不一致的情况。从表现形式上来看,它是程序的一部分或一个程序 (比如 SQL 中的事务就是由一条或多条 SQL 语句组成的)。理想的办法是可靠地保证集合中的每个操作都可以正确地执行,但实际上系统不可能提供这样的保证。所以 DBMS 不得不引入一套机制来提供另一种相当可靠的保证,即事务管理器(Transaction Manager)。所以尽管事务本质上不是原子的,但它具有原子特性。此外 DBMS 在执行事务时,还会面临一系列其他的棘手问题。
而 ACID 理论之所以重要,是因为它不仅为解决这些问题提供了理论指导,还被各个 DBMS 产品真正地实现了,所以逐渐变成了 DBMS 必须具有的核心特性。因此应用开发者可以将数据的可靠性问题交由 DBMS 解决,这简化了应用程序的设计和实现。
什么是 ACID
原子性(事务的完整性)
原子性(Atomicity)表示事务是原子的,其中的所有操作,要么全部完成,要么都不完成。以下示例是理解原子性的一个经典案例(来自《Database System Concepts》):
read(A)
A := A - 50
write(A)
read(B)
B := B + 50
write(B)
该示例展示的是一个转账的事务,表示从 A 账户转账 50 元到 B 账户。原子性要求整个转账步骤要么全部执行,那么都不执行,而不能只从 A 账户扣除了 50 元,但没有转账到 B 账户中。
原子性意味着事务是一个完整性单元:即它的功能逻辑必须是完整的,原子特性是由 DBMS 的恢复系统(Recovery System) 来保证(实现恢复系统的思路是依赖于预写日志(WAL,Write-Ahead Logging)机制,DBMS 会在持久存储介质中维护一个日志文件,在对数据进行实际修改之前,先将所有更改记录到日志中。当系统发生故障时,通过日志记录可以确定哪些事务已提交,哪些事务未完成,以便进行相应的恢复操作)。
一致性(事务的逻辑性)
一致性(Consistency)表示事务完成后,数据库必须从一个一致状态转换到另一个一致状态,而在事务执行的过程中,数据可以处于不一致性的中间状态,并且转换过程不会破坏数据库的完整性约束。
还是以上面的事务为例,一致性要求事务执行前后,A 和 B 账户的总金额是不变的,这一点很好理解,但也容易带来混淆。原子性能保证整个事务要么全部执行,那么都不执行,已经能保证 A 和 B 账户的总金额不变了,为什么还需要提出一致性的要求?
严格来说,数据库的一致性指的是数据库的完整性约束,数据一致等价于不破坏了数据库的完整性约束:
如果数据库的完整性约束检查是即时进行的,则应该在每条语句执行后立即检查完整性约束,而不是在整个事务结束之后“延迟”检查。在这种情况下,数据库总是处于一致性状态。
如果数据库的完整性约束检查是延迟进行的,一旦事务违背了任何约束则必然会回滚,等价于事务未执行,此时数据库必然还是处于一致状态。
无论哪种情况,特别说明事务具有一致性属性都是多余的 。所以这里的一致性实际上指的是数据的正确性,部分文献会将事务的 Consistency 属性描述为正确性(Correctness) 。DBMS 只能保证数据的一致性,但不能保证数据的正确性(数据正确意味着数据满足一致性,反之不然),正确性并不是事务真正的特性,而是一种期望,并且只能由开发者保证:比如上述示例中“总金额必须不变”这个要求是编写事务的人员按照现实情况强制赋予的。很难在 DBMS 中定义这种完整性约束,所以只能由开发者手动地将整个转账操作放在一个事务中,再由 DBMS 的事务机制(比如原子性)保证这种正确性。
一致性意味着事务是一个逻辑工作单元,它的逻辑是否正确只能由开发者来保证,但 DBMS 提供的完整性约束检测可以起到辅助作用。
隔离性(事务可以并发)
隔离性(Isolation)表示 DBMS 可以支持多个事务并发执行,事务之间相互隔离,互不干扰,一个事务的中间状态对其他事务不可见。
在实现事务机制时,保持多个事务之间的数据一致性是需要付出性能代价的 (比如会迫使一个事务等待另一个事务) ,所以就需要在数据一致性和并发性能之间进行权衡。 隔离级别(Isolation Level) 就是用于决定一个事务与其他并发事务之间数据可见性的程度。隔离级别越高,事务间的隔离性越强,而并发性能越低。隔离级别越低,并发性能越高,但数据不一致性的问题越严重。
所以除非数据库处于真正的最高隔离级别下,否则事务之间就不是真正的相互隔离。在使用较低的隔离级别时,如果业务场景可以容忍数据的不一致,当然是最好不过的了,否则就需要开发者做额外的工作来保证数据一致。
隔离性意味着事务是一个并发单元 ,它是由 DBMS 的 并发控制系统(Concurrency Control System) 来保证(常见的策略包括封锁机制、时间戳机制、MVCC)。
持久性(事务可被恢复)
持久性(Durability)表示事务一旦提交,它对数据库的更改就会被永久保存,即使系统发生崩溃也不会丢失。
持久性意味着事务是一个恢复单元,它也是由 DBMS 的 恢复系统 来保证。
ACID 是理想也是目标
遗憾的是,由于实际情况的限制,100% 保证这些特性是做不到的:
一致性实际上是由开发者负责保证的,而开发者可能会犯错,可能会编写错误的逻辑代码。
WAL 日志虽然可以起到冗余的保护作用,但 WAL 本身还是记录在存储介质当中,一旦存储介质,还是会导致 WAL 日志的损坏,进一步的破坏原子性和持久性。
出于性能上的考虑,DBMS 有时不得不在隔离性上做妥协,没有实现真正的相互隔离,互不干扰。
小结
虽然 ACID 是对 DBMS 提出的要求,但这些问题不是关系模型才会面临的,也不只是数据库领域才需要解决的,比如操作系统内核通常会提供了创建目录的 API,这些 API 会修改文件系统内部的数据结构,如果 API 在执行期间被中断,则也可能会导致内部数据结构处于不一致的错误状态,文件系统完全可以提供类似的恢复机制。所以关键点在于系统是否愿意付出一些代价来提供这种保证 (比如性能降低,复杂度提高)。