# 系列目录

# 参考

# 事务概念

数据库事务 (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 不支持):事务只会查询到在当前事务开始前已提交事务影响的数据;但在插入数据时会查询到当前事务开始后提交的事务影响的数据(当前事务查询到不存在 id=1 的数据,过程中其他事务增加 id=1 的数据并提交,当前事务插入 id=1 时报错冲突,但的确又查不到 id=1 的数据),被称为幻读。
  • 序列化(Serializable):如果要彻底完全解决上面的三种问题,就只能让事务串行化了,也就是把多个事务变成一个序列。

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

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

* InnoDB 提供了在可重复读下回避幻读的方法:如果在可重复读下进行了 SELECT ... FOR UPDATEUPDATEDELETE,InnoDB 会锁住当前最新已提交的数据(而不是当前事务开始前已提交的数据),并在查询、更新、删除过程中使用最新已提交的数据。(代价是,加锁后读的值和加锁前读的值不同,即不可重复读)。-- locking - How MVCC works with Lock in MySql? - Stack Overflow (opens new window)

picture 3