# 系列目录

# 参考

# 事务概念

数据库事务 (transaction) 是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。

最常见的例子是转帐:从帐户 A 转 100 元到帐户 B 上,该事务的操作有:

  1. A 账户中减 100 元;
  2. B 账户中加 100 元。

这两个步骤,要么全做,要么全不做。

事务全做完以后,需要进行提交(commit)。提交以后,事务做的修改就会被永久记录在数据库中,即使发生故障也能恢复。

如果事务做到一半,不想做了/做不下去了,可以回滚(roll back),回到什么都没做的状态。

# 事务的 ACID

ACID,是指数据库管理系统 (DBMS) 在写入或更新资料的过程中,为保证事务 (transaction) 是正确可靠的,所必须具备的四个特性:

  • 原子性 (atomicity):一个事务要么全做要么全不做;InnoDB 通过 undo log 保证回滚时能找到之前的数据。
  • 一致性 (consistency):任何时刻,数据处于一种有意义的状态,这种状态是语义上的而不是语法上的。还是转账的例子:从帐户 A 转 100 元到帐户 B 上,如果帐户 A 上的钱减少了,而帐户 B 上的钱却没有增加,那么我们认为此时数据处于不一致的状态;InnoDB 通过 crash recovery 和 double write buffer 保证。(todo: cache recovery, double write buffer)
  • 隔离性 (isolation):一个事务不影响其他事务的运行效果;下面会详细聊。
  • 持久性 (durability):事务一旦提交,则其结果就是永久性的,即使故障也能恢复;InnoDB 通过 redo log 保证提交的数据一定不丢失。

# 事务的原子性和持久性的保证

  1. 将所有事务开始、提交、终止,以及数据的更新操作(记录数据更新前的值即前像,或更新后的值即后像)计入 log
  2. 系统崩溃后重启,先读取日志对已提交的事务进行 redo(保证持久性)
  3. 然后对尚未提交的的事务进行 undo(保证原子性)

# InnoDB 事务隔离级别

SQL 标准定义了四种隔离,隔离程度由低到高依次为:

  • 读未提交/脏读(Read Uncommitted):事务会查询到其他未提交事务影响的数据,也被称为脏读;
  • 读提交/不可重复读(Read Committed,Oracle 默认):事务只会查询到其他已提交事务影响的数据;但多次查询同一个数据,其结果可能变化(如果一个事务两次读同一个数据之间,某个事务对这个数据修改并提交了,读事务会发现两次读到的东西不一样),这就被称为不可重复读。
  • 可重复读(Repeatable Read,InnoDB 默认,Oracle 不支持):事务只会查询到其他已提交事务影响的数据,且开始后查询不到其他事务的修改操作;但可以查询到其他事务的增删操作,同一个查询多次执行,其结果可能变化(如两次查询表中的所有内容,过程中其他事务增/删并提交,导致两次查询的内容不同),这就被称为幻读。
  • 序列化(Serializable):如果要彻底完全解决上面的三种问题,就只能让事务串行化了,也就是把多个事务变成一个序列。

从学术的角度来讲,隔离程度越高越好;但是工业的角度来讲,隔离程度越高,事务并发度更低,且更容易死锁。所以,在实际中可能需要根据实际情况,选择性能和隔离性的一个平衡点。

有/无问题 脏读 不可重复读 幻读
读未提交
读提交
可重复读 有*
序列化

* InnoDB 使用“快照读”回避了这个问题,详见 MySQL 多版本并发控制 部分。