本章小结
事务是一个抽象层,允许应用程序假装某些并发问题和某些类型的硬件和软件故障不存在。各式各样的错误被简化为一种简单情况:事务中止(transaction abort),而应用需要的仅仅是重试。
在本章中介绍了很多问题,事务有助于防止这些问题发生。并非所有应用都易受此类问题影响:具有非常简单访问模式的应用(例如每次读写单条记录)可能无需事务管理。但是对于更复杂的访问模式,事务可以大大减少需要考虑的潜在错误情景数量。
如果没有事务处理,各种错误情况(进程崩溃,网络中断,停电,磁盘已满,意外并发等)意味着数据可能以各种方式变得不一致。例如,非规范化的数据可能很容易与源数据不同步。如果没有事务处理,就很难推断复杂的交互访问可能对数据库造成的影响。
本章深入讨论了并发控制的话题。我们讨论了几个广泛使用的隔离级别,特别是读已提交,快照隔离(有时称为可重复读)和可序列化。并通过研究竞争条件的各种例子,来描述这些隔离等级:
脏读
一个客户端读取到另一个客户端尚未提交的写入。读已提交或更强的隔离级别可以防止脏读。
脏写
一个客户端覆盖写入了另一个客户端尚未提交的写入。几乎所有的事务实现都可以防止脏写。
读取偏差(不可重复读)
在同一个事务中,客户端在不同的时间点会看见数据库的不同状态。快照隔离经常用于解决这个问题,它允许事务从一个特定时间点的一致性快照中读取数据。快照隔离通常使用多版本并发控制(MVCC) 来实现。
更新丢失
两个客户端同时执行读取-修改-写入序列。其中一个写操作,在没有合并另一个写入变更情况下,直接覆盖了另一个写操作的结果。所以导致数据丢失。快照隔离的一些实现可以自动防止这种异常,而另一些实现则需要手动锁定(SELECT FOR UPDATE
)。
写偏差
一个事务读取一些东西,根据它所看到的值作出决定,并将决定写入数据库。但是,写作的时候,决定的前提不再是真实的。只有可序列化的隔离才能防止这种异常。
幻读
事务读取符合某些搜索条件的对象。另一个客户端进行写入,影响搜索结果。快照隔离可以防止直接的幻像读取,但是写入歪斜环境中的幻影需要特殊处理,例如索引范围锁定。
弱隔离级别可以防止这些异常情况,但是让应用程序开发人员手动处理其他应用程序(例如,使用显式锁定)。只有可序列化的隔离才能防范所有这些问题。我们讨论了实现可序列化事务的三种不同方法:
字面意义上的串行执行
如果每个事务的执行速度非常快,并且事务吞吐量足够低,足以在单个CPU核上处理,这是一个简单而有效的选择。
两阶段锁定
数十年来,两阶段锁定一直是实现可序列化的标准方式,但是许多应用出于性能问题的考虑避免使用它。
可串行化快照隔离(SSI)
一个相当新的算法,避免了先前方法的大部分缺点。它使用乐观的方法,允许事务执行而无需阻塞。当一个事务想要提交时,它会进行检查,如果执行不可序列化,事务就会被中止。
本章中的示例主要是在关系数据模型的上下文中。使用关系数据模型。但是,正如在讨论中,无论使用哪种数据模型,如“多对象事务的需求”中所讨论的,事务都是重要的数据库功能。
本章主要是在单机数据库的上下文中,探讨了各种概念与想法。分布式数据库中的事务,则引入了一系列新的困难挑战,将在接下来的两章中讨论。