数据库事务

引:数据库事务在数据库操作中是十分重要的,因为它要么全部执行,要么全部执行,这样才能保证数据库的数据的完整性与一致性。

什么是事务

构成单一逻辑工作单元的操作集合。

事务是访问并可能更新各种数据项的一个程序执行单元,事务通常用形如begin transaction 和 end transaction来界定,事务由它们之间执行的全体操作组成。

举个例子:将钱从一个账户转到另一个账户就是一个事务,该事务包括分别对两个账户的更新。

ACID特性

  1. 原子性(Atomicity):事务是一个不可分割的操作,要么全都正确执行,要么全都不执行。
  2. 一致性(Consistency):事务开始前和事务结束后,数据库的完整性约束没有被破坏。比如上面转账例子中,两个账户的总金额就是完整性约束,因为他们的总金额是不变的。但是在并发事务中,一致性很容易被破坏,需要我们特别注意。
  3. 隔离性(Isolation):事务的执行是相互独立的,它们不会相互干扰,一个事务不会看到另一个正在运行过程中的事务的数据。
  4. 持久性(Durability):事务结束后,事务的结果必须是永久保存的。即使数据库发生崩溃,在数据库恢复后事务提交的结果仍然不会丢失。

确保隔离性有可能对系统性能造成较大的不利影响,由于这个原因,一些应用在隔离性上采取一些妥协。

事务的分类

  1. 扁平事务(Flat Transactions):它是实际生产环境中最常用、最简单的事务类型。在扁平事务中,所有操作都处于同一层次,其由begin transaction开始,commit或rollback结束,其间的操作是原子的,要么都执行,要么都回滚,因此扁平事务是应用程序称为原子操作的的基本组成模块。但是发生错误时都需要回滚到事务的起始位置,无法回滚部分操作,所以回滚开销太大。
  2. 带有保存点的扁平事务(Flat Transactions with Savepoints): 除了支持扁平事务支持的操作外,允许在事务执行过程中回滚同一事务中较早的一个状态。这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎要求,开销太大,保存点用来通知事务系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。
  3. 链事务(Chained Transactions):在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务,提交事务操作和开始下一个事务操作 将合并为一个原子操作,这意味着下一个事务将看到上一个事务的结果,就好像一个事务中进行的一样。和带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点,而链事务中的回滚仅限当前事务,即只能恢复到最近的一个保存点。
  4. 嵌套事务(Nested Transactions):由一个顶层事务(top-level transaction)控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务。嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务也可以是扁平事务,处在叶节点的事务是扁平事务,但是每个事务从根到叶节点的距离可以说是不同的。子事务既可以提交也可以回滚。但是它的提交操作并不马上生效。除非其父事务已经提交。树中的任意事务回滚会引起它的所有子事务一同回滚,故子事务仅保留ACI特性而不具有D特性。

  5. 分布式事务(Distributed Transactions):通常是一个分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。

数据库并发访问会出现的问题

读问题

  1. 脏读:A事务读取B事务尚未提交的更改数据,并在这个数据的基础上进行操作,这时候如果事务B回滚,那么A事务读到的数据是不被承认的。
  2. 虚度(不可重复读):A事务读取了B事务已经提交的更改数据。假如A在取款事务的过程中,B往该账户转账100,A两次读取的余额发生不一致。防止读到更改数据,需要对操作的数据添加行级锁。
  3. 幻读:A事务读取B事务提交的新增数据。例如银行系统在同一个事务中两次统计存款账户的总金额,在两次统计中,刚好新增了一个存款账户,存入了100,这时候两次统计的总金额不一致。 防止读到新增数据,需要对操作的数据添加表级锁。

更新问题

  1. 第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了。
  2. 第二类丢失更新:A事务覆盖B事务已经提交的数据,造成B事务所做的操作丢失。

事务隔离级别

为了解决上述问题,数据库通过多种并发控制机制(常用的有两阶段封锁和快照隔离)解决并发访问的问题。但是直接使用并发控制机制是很复杂的,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的sql语句然后自动选择合适的并发控制机制。

四种隔离级别如下:

  1. 可串行化(SERIALIZABLE):保证可串行化调度,不允许出现任何问题。
  2. 可重复读(REPEATABLE READ) :只允许读取已提交的数据,而且在一个事务两次读取一个数据项期间,其他事务不能更新该数,单不要求该事务与其他事务可串行化。
  3. 已提交读(READ COMMITTED):只允许读取已提交的数据,但不要求可重复读。
  4. 未提交读(READ UNCOMMITTED):允许读取未提交的数据,这是SQL允许的最低一致性级别。

不同的隔离级别对并发问题的解决情况如下图:

隔离级别 脏读 虚读 幻读 第一列丢值更新 第二类丢失更新
可串行化(SERIALIZABLE) 不允许 不允许 不允许 不允许 不允许
可重复读(REPEATABLE READ) 不允许 不允许 允许 不允许 不允许
已提交读(READ COMMITTED) 不允许 允许 允许 不允许 允许
未提交读(READ UNCOMMITTED) 允许 允许 允许 不允许 允许

参考

  1. 《数据库系统概念》
  2. 数据库事务详解
  3. 数据库并发事务存在的问题(脏读、不可重复读、幻读等)
  4. 数据库的事务与并发控制
  5. MySQL中事务的分类
  6. 数据库隔离级别及其实现原理