事务就是将一组语句(或者说操作)打包成一个逻辑单元进行执行,并提供一种保证,这一组操作要么全部成功( commit,应用到数据库里),要么全部失败(被动 abort,或者主动 rollback),而不会存在只执行了一半的中间状态。此外,如果多个客户端的事务并发执行,会涉及到隔离性的问题,一般来说,数据库允许用户在隔离级别和性能之间做选择。

时间空间两个角度来理解事务,从生命周期(时间)来讲,事务要保证一组操作的整体性;从并发控制(空间),事务要做好多个事务间的并发控制。

本章首先讨论事务的基本概念,然后针对隔离级别(并发控制)做了详细探讨,包括读已提交、快照隔离和可串行化。事务保证和是否分布式在概念上相对正交,但在实现上,分布式系统中事务的实现难度要大的多。

深入理解事务

ACID

原子性:在发生错误时,会回滚该事务所有已经写入的变更。

一致性:数据库在应用程序的视角处于某种”一致性的状态“

隔离性:每个事务的执行是互相隔离的,每个事务都可以认为自己是系统中唯一正在运行的事务 持久性:即事务一旦提交,即使服务器宕机重启,已经提交的事务所写入的数据就不会丢失。

单对象和多对象操作

在事务中,只修改一个对象为单对象操作,否组为多对象操作。

如下图,用户 1 插入一封邮件,然后更新未读邮件数;用户 2 先读取读取邮件列表,后读取未读计数。但邮箱列表中显示有新邮件,但未读计数却显示 0。

image.png

隔离性可以避免此问题,使用户 2 要么看到用户 1 的所有更新,要么看不到任何更新。

下图,说明了原子性保证。如果是事务执行过程中发生错误,原子性会保证如果计数器更新失败,新增的邮件也会被撤销。

在多对象事务中,一个关键点是如何确定多个操作是否属于同一事务:

  1. 物理上来考虑。可以通过 TCP 连接来确定,在同一个连接中,BEGIN TRANSACTION 和 COMMIT语句之间的所有内容,可以认为属于同一个事务。但会有一些 corner case,如在客户端提交请求后,服务器确认提交之前,网络中断,连接断开,此时客户端则无从得知事务是否被成功提交。
  2. 从逻辑上来考虑。使用事务管理器,为每个事务分配一个唯一标识符,从而对操作进行分组。

几种弱隔离级别

读已提交

性能最好的隔离级别就是不上任何锁,但会存在脏读脏写的问题。为了避免脏写,可以给要更改的对象加长时写锁,但读数据时并不加锁,此时的隔离级别称为读未提交(RU,Read Uncommitted)。但此时仍然会有脏读,为了避免脏读,可以对要读取的对象加短时读锁,此时的隔离级别是读已提交(RC,Read Committed),他提供了两个保证:

  1. 从数据库读取时,只能读到已经提交的数据(即没有脏读,no dirty reads)