《设计数据密集型应用》第七章(1) 事务:基础概念
前言
在设计数据系统时,由于数据库、应用程序、网络的问题,或者是服务器端、客户端的问题,以及并发修改的情况,很可能出现数据出错的情况。
事务的提出是为了简化这些异常的处理,应用可以将读写的请求合并到一组,构成一个逻辑单元,提交到数据库中。 数据库会将逻辑单元的所有操作视为一个操作,要么成功,要么失败回滚。因此,应用程序可以在事务执行失败时,安全地进行重试,应用程序可以不必担心事务中的操作部分执行成功,部分失败。
虽然事务的概念已经存在了很长时间,但不应把它当做是理所当然的,因为事务并不符合自然法律,而只是为了简化应用程序的开发而设计的特性。并不是所有的应用程序都需要事务,有时我们可以弱化,甚至禁用事务的特性,或者通过一些其他方式实现一些安全特性。
在本章的介绍中,我们会深入到并发控制、不同类型的竞争条件,以及数据库如何实现不同的隔离等级,比如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的形式写一份,可以在数据故障时恢复。在分布式数据库中,持久性的含义是数据会被复制多份,保存在集群中的其他节点,数据库必须等所有的副本都写完后,才会确认写入成功。
下一节我们将继续介绍事务的操作。