前言
在设计数据系统时,由于数据库、应用程序、网络的问题,或者是服务器端、客户端的问题,以及并发修改的情况,很可能出现数据出错的情况。
事务的提出是为了简化这些异常的处理,应用可以将读写的请求合并到一组,构成一个逻辑单元,提交到数据库中。 数据库会将逻辑单元的所有操作视为一个操作,要么成功,要么失败回滚。因此,应用程序可以在事务执行失败时,安全地进行重试,应用程序可以不必担心事务中的操作部分执行成功,部分失败。
虽然事务的概念已经存在了很长时间,但不应把它当做是理所当然的,因为事务并不符合自然法律,而只是为了简化应用程序的开发而设计的特性。并不是所有的应用程序都需要事务,有时我们可以弱化,甚至禁用事务的特性,或者通过一些其他方式实现一些安全特性。
在本章的介绍中,我们会深入到并发控制、不同类型的竞争条件,以及数据库如何实现不同的隔离等级,比如read commited,snapshot isolation和serializability。
事务的概念
1975年,第一个SQL数据库-R,由IBM开发,其中包含了事务的最初的思想。在随后的其他关系型数据库,比如MySQL、PostgreSQL等,虽然事务的实现细节不断变化,但事务的概念并没有变化。随着2000年后,NoSQL数据库的流行,一些数据库不再严格遵照事务的概念,做了某些安全性的弱化,也引起了关于是否需要事务的辩论。
我们先来看一下事务的概念是什么。
ACID的含义
我们常说的事务的安全性保证,通常等价于一个广泛流传的缩写:ACID。ACID分别代表原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
每个数据库的ACID实现可能是不一样的,比如对隔离性的含义就有很多的分歧。因此,一个声明实现了ACID的数据库,我们并不能确定可以得到什么样的保证。
下面我们来具体的看下每个单词的含义。
Atomicity
原子是最小不能分割的部分,在不同的领域的含义还不一样。比如,在多线程编程中,一个线程执行原子操作,指的是其他线程并不能看到该线程执行一半的结果。
在数据库中,原子性是指如果将多个操作放在一个事务中,它们只能全部执行成功,或全部执行失败。如果在执行中间出错,将丢弃或者回滚之前已经完成的操作。
Consistency
一致性的概念已经被无数次的被重用了,我们先来整理一下各种不同的一致性:
- 第5章中,我们讨论了副本一致性(replica consistency)和异步系统的最终一致性(eventual consistency)
- 一致性哈希(consistency hashing)是一种系统分区rebalance的方法
- 第9章的CAP理论中,一致性(consistency)用来定义线性一致性
- 在ACID中,一致性(consistency)指的是特定应用的数据库处在一种“好状态”
一致性的概念是数据中始终存在一个不变的值,比如在会计系统中,所有用户的存款和贷款始终是相等的。如果事务执行开始时,不变量的值不变,在事务执行完成时,该值也不会改变,则实现了一致性的要求。
由于不变值是应用程序定义的,数据库并不能做什么保证。当我们破坏一致性条件时,数据库并不能阻止我们。数据库只能做很少的一些事情,比如外键限制,或者唯一性限制等。因此,在ACID中,AID都是数据库保证的,一致性是由应用程序保证的。
Isolation
在数据库并发请求时,并发读写不同部分的数据并不会有什么问题。当并发写相同的数据时,可能会出现如下的并发问题,也可以称为竞争条件:
隔离性的含义是并发执行的事务各自隔离,并不影响互相执行的结果。隔离性也有一种提法叫串行化,也就是每个事务都好像是运行在整个数据库的唯一事务,所有事务提交的结果,就好像是事务是串行提交的一样。
但在实际中,串行的隔离性很少使用,因为对性能的影响较大。一般我们会用一种弱一点的隔离性,snapshot isolation,后面会继续介绍。
Durability
持久性是数据在写入数据库中,将不会丢失,即使出现了硬件故障,或者数据库崩溃。
在单机数据库中,数据在写入硬盘或者SSD之后,也会以write-ahead log的形式写一份,可以在数据故障时恢复。在分布式数据库中,持久性的含义是数据会被复制多份,保存在集群中的其他节点,数据库必须等所有的副本都写完后,才会确认写入成功。
下一节我们将继续介绍事务的操作。