XIII. Transactions

Transactions

  • 事务(transaction):事务是程序执行的一个单元,通常包含一组 SQL 语句.

    • 在事务执行期间,数据库可能不一致;事务提交之后,数据库应当处于一致状态.

    • 需要处理的两个主要问题:

      • 失效(failure):事务可能因为某些错误而失效,需要回滚.

      • 并发执行(concurrent execution):多个事务的并发执行.

  • 事务的 ACID 特性

    • 原子性(atomicity)事务的所有步骤只能完全执行或完全不执行

    • 一致性(consistency)隔离执行事务以保证数据库的一致性.(在编写事务的时候就需要确保其一致性.)

    • 隔离性(isolation):事务在并发执行的时候不能感知到其他事务正在执行(不能相互影响).\Rightarrow 执行的中间结果对于其他事务来说是不可见的

    • 持久性(durability):事务执行完成后,其对数据库所做的修改应该持久化地存储在数据库中(需要搞到非易失性存储上,或者在失效后能够通过已经放到非易失性存储上的日志进行恢复),即使软硬件发生故障.

从数据库系统的角度看事务的 ACID 特性 @NoughtQ

mem注:主要是提前 overview 了一下后面的知识,读一遍即可不需要写到 cheatpaper 上(有的也不是考点).

  • 原子性

    对于数据库系统来说,有良中行方法来确保事务的原子性.

    • 日志(logging)

      • DBMS 记录所有操作,以便在事务中止时能够撤销这些操作

      • 它同时在内存和硬盘上维护撤销记录

      • 出于审计与效率的考虑,几乎所有现代系统都采用日志机制

    • 影子页(shadow paging)

      • DBMS 会为事务修改的页创建副本,而事务则对这些副本进行更改;只有当事务提交时,这些页面的改动才会对外可见

      • 这种方法在运行时通常比基于日志的 DBMS 速度更慢

      • 然而,其优势在于:若仅采用单线程操作,则无需记录日志,因此当事务修改数据库时写入磁盘的数据量更少;这也使得恢复过程变得简单——只需删除所有未提交事务所涉及的页面即可

      • 但总体而言,由于人们更看重运行时的性能表现而非恢复效率的提升,所以实践中很少采用这种方案

  • 一致性

    从高层次来看,一致性意味着数据库所代表的”世界”在逻辑上是正确的.应用程序对数据提出的所有问题(即查询)都将返回逻辑上正确的结果.存在两种关于一致性的概念:

    • 数据库一致性:数据库精确地反映了其所建模的现实世界实体,并遵循完整性约束.(例如,一个人的年龄不可能为负数).此外,未来的事务应当能够看到数据库中过去已提交事务所产生的效果.

    • 事务一致性:如果在事务开始之前数据库是一致的,它也会在之后保持一致.确保事务一致性是应用程序的责任.

  • 持久性

    事务的持久性需要通过数据库系统的 恢复系统(recovery system) 进行维护.要保证持久性,需要做到:

    • 由事务实现的更新在事务结束前被写到硬盘上.

    • 由事务实现的关于更新的信息也要被写到硬盘上,并且这个信息应该足以使数据库在遇到故障重启后能够重构更新.

  • 隔离性

    事务的隔离性是通过数据库系统中的 并发控制系统(cocurrent-control system) 维护的.

    并发执行多个事务可能会导致这些事务操作的交错,这是我们不想看到的.一种解决方法是干脆让这些事务串行执行,但这样的话相比并发执行性能太拉胯了,所以实际的解决方案还是确保并发执行,但能够确保并发执行事务的结果和等价的按照时间顺序执行事务的结果是一样的.

  • 事务状态(transaction state):事务必须处于以下状态之一:

    • active:初始状态.

      • 执行中的事务都会保持在这个状态
    • partially committed:在最后一句指令被执行之后.

      • 此时要输出的结果可能还在内存/buffer中,而没有写入非易失性存储中或是因为日志还没有持久化.

      • partially committed 的事务也可能进入 failed 状态从而回滚.

    • failed:在发现事务执行失败之后.

      • 这样的事务必须回滚而使其进入 aborted 状态.
    • aborted在事务被回滚结束之后,相当于已经回复了初始状态.

      • 这时数据库系统可以选择 重启(restart)杀死(kill) 事务.
    • committed:事务被完整的执行并提交.

      • 已经提交的事务不能被回滚,或在恢复时通过 redo 消除其影响.
  • 并发执行中的异常(anomalies in concurrent execution):后面会具体介绍.

丢失修改(lost update)

事务覆写了另一个并发事务未提交的数据

脏读(dirty read)

在事务提交其更改之前,事务看到了另一个事务的写效果.

不可重复读(unrepeatable read)

事务多次读取相同数据项,得到的是不同的值

幻读(phantom problem)

和 unrepeatable read 的类似,但强调的范围查询的结果不一样,而非值的更新;或者说区别在于数据项的有无而非数据项的值.

Schedules

  • 调度(schedule):一系列用于指定并发事务的执行顺序的指令.

    • 需要包含事务中的所有指令.

    • 需要保证单个事务中的指令的相对顺序.

    • 成功执行的事务将 commit 作为最后一条指令;未能成功执行的事务将 abort 作为最后一条指令.

Conflict Serializability

  • 串行调度(serial schedule):当且仅当一个事务调度完成之后再进行下一个(也就是没有并行).

  • 等价调度(equivalent schedule):改变处理的顺序,但是执行结果等价.对于”等价”的不同定义,引出了冲突可串行化和视图可串行化两种不同的概念.

  • 如果调度 SS 可以通过交换一系列相邻且不冲突的指令得到调度SS';换句话说,SSSS' 涉及相同事务的相同操作,并且每对冲突操作在两个调度中的顺序一致,则称 SSSS'冲突等价的(conflict equivalent)

    • 冲突操作:R-W、W-R、W-W.
  • 如果调度 SS 冲突等价于一个串行调度,则称 SS冲突可串行化的(conflict serializable)

  • 优先图(precedence graph):对于调度 SS,其优先图 G=(V,E)G = (V,E) 的每条边 TiTjT_{i} \rightarrow T_{j} 表示任何与 SS 等价的串行调度 SS' 中,TiT_{i} 一定出现在 TjT_{j} 之前.

    • 这以为着以下三种条件之一成立(相当于除了 RAR 之外的 RAW、WAR、WAW 三中情况):

      • TiT_{i}TjT_{j} 执行 read(Q) 之前执行 write(Q)

      • TiT_{i}TjT_{j} 执行 write(Q) 之前执行 read(Q)

      • TiT_{i}TjT_{j} 执行 write(Q) 之前执行 write(Q)

  • 调度 SS 是冲突可串行化的当且仅当其优先图 GG 中没有环,可通过 拓扑排序(topological sorting) 判断.

  • 可以通过拓扑排序得到一个 可串行性顺序(serializability order)

  • 视图可串行化(view serializability):比冲突可串行化更松,只需要满足以下三个条件:

    • 初始读(inital read)(第一个读数据的相同):在调度 SS 中,如果事务 TiT_{i} 读取了数据项 QQ 的初始值(即在 TiT_{i} 读取之前,没有任何其他事务写过 QQ),那么在调度 SS' 中,也必须是 TiT_{i} 读取 QQ 的初始值.

    • 读来源(read-from)(每个读操作读到的是同一个事务写的数据):在调度 SS 中,如果事务 TiT_{i} 读取的 QQ 值是由事务 TjT_{j} 写入的,那么在调度 SS' 中,TiT_{i} 也必须读取由 TjT_{j} 写入的 QQ 值.

    • 最终写(final write)(数据库的最终版本相同):在调度 SS 中,如果事务 TiT_{i} 是最后一个对数据项 QQ 执行写操作的,那么在调度 SS' 中,也必须是 TiT_{i} 执行对 QQ 的最终写.

  • 如果调度 SS 与另一个串行调度视图等价,则称 SS视图可串行化 的.

  • 每个冲突可串行化的调度都是视图可串行化的.

Recoverable Schedules

  • 可恢复调度(recoverable schedule):如果事务 TjT_{j} 读取了事务 TiT_{i} 读取的数据,那么 TiT_{i} 的提交操作必须出现在 TjT_{j} 的提交操作之前.
可恢复调度的一个例子 @NoughtQ

考虑以下调度:

由于事务 T6T_{6} 没有包含 commitabort 操作,因此称这样的调度为 部分调度(partial schedule)

假如 T6T_{6} 在提交发生前故障,由于 T7T_{7} 依赖于(dependent) T6T_{6}T7T_{7} 读取 T6T_{6} 的写入值),所以此时 T7T_{7} 必须中止.然而 T7T_{7} 已经提交了,无法中止,所以我们遇到了无法正确从 T6T_{6} 故障中恢复的情况.像这样的调度就不是可恢复调度.

  • 级联回滚(cascading rollback):即便调度是可恢复的,在故障恢复过程中仍可能需要回滚多个事务.这种现象成为级联回滚.

  • 无级联调度(cascadeless schedule):如果 TjT_{j} 需要读取 TiT_{i} 事先写入的数据项,那么 TiT_{i} 必须在这一读操作之前提交.

    • 每个无级联调度都是可恢复的.

    • 可以将调度限制为无级联调度.

Transaction Isolation Levels

  • 事务隔离等级(transaction isolation level)

    • 可串行化(serializable):通常能确保可串行化执行,但有些数据库系统在实现这个隔离等级时,会允许一些不可串行化的执行.

    • 可重复读(repeatable read):仅允许读取已提交的数据,并且要求某个事务对同一数据的两次读取之间,不允许其他事务对其更新.

    • 读已提交(read committed):仅允许读取已提交的数据,但不要求可重复读取.

    • 读未提交(read uncommitted):允许读取未提交的数据,它是最低级的隔离等级.

  • 所有隔离等级都不允许 脏写(dirty write),即事务不能对另一个事务未提交或中止写入的数据项进行写入.

  • 许多数据库系统默认运行在 read committed 的提交等级上.

Comments