事务广泛应用于订单系统、银行系统等多种场景
A给B转账500,需要做以下几件事:
- 检查 A 的账户余额 > 500
- A 扣 500
- B 加 500
正常流程走下来,A扣500,B 加 500,皆大欢喜
如果 A 扣了后,系统出故障了,A 损失 500,而 B 也没有收到本该属于他的 500
以上的案例中,隐藏着一个前提条件:A 扣钱和 B 加钱,要么同时成功,要么同时失败
事务的需求就在于此
事务是什么?
与其给事务定义,不如说一说事务 ACID 四个特性
- A(atomicity) 一个事务的执行被视为一个不可分割的最小单元。事务里面的操作,要么全部成功执行,要么全部失败回滚,不可以只执行其中的一部分
- C(consistency)一个事务的执行不应该破坏数据库的完整性约束。如果上述例子中第 2 个操作执行后系统崩溃,保证 A 和 B 的金钱总计是不会变的
- I(isolation) 通常来说,事务之间的行为不应该互相影响。而实际上事务相互影响的程度受到隔离级别的影响
- D(durability) 事务提交之后,需要持久化到磁盘。即使系统崩溃,提交的数据也不应该丢失
事务的四种隔离级别
可以认为是事务的 “自私” 程度,它定义了事务之间的可见性
1.READ UNCOMMITTED(未提交读)
事务 A 对数据做的修改,即使没有提交,事务 B 也可见,这种问题叫脏读
隔离程度较低,实际运用中会引起很多问题,因此一般不常用
2.READ COMMITTED(提交读)
不会脏读,但会不可重复读,事务 A 对数据做的修改,提交之后会对事务 B 可见
例
事务 B 开启时读到数据 1,接下来事务 A 开启,把这个数据改成 2,提交
B 再次读取这个数据,会读到最新的数据
这是许多数据库的默认隔离级别
3.REPEATABLE READ(可重复读)
不会不可重复读,但会幻读
事务 A 修改,提交后,对于先于事务 A 开启的事务是不可见的
例
事务 B 开启时读到数据 1,接下来事务 A 开启,把这个数据改成 2,提交
B 再次读取这个数据,仍然只能读到 1
幻读意思是,某事务读取某个范围内的值的时候,另外一个事务在这个范围内插入了新记录,之前的事务再次读取这个范围的值,会读取到新插入的数据
Mysql 默认隔离级别是 RR,而 innoDB 引擎间隙锁解决了幻读的问题
4.SERIALIZABLE(可串行化)
强制要求所有事物串行执行,在这种隔离级别下,读取的每行数据都加锁,会导致大量的锁征用问题,性能最差
理解四种隔离级别
事务 A 和事务 B 先后开启,并对数据 1 进行多次更新

四个小人在不同的时刻开启事务,可能看到数据 1 的哪些值呢?
- 第一个
可能读到 1-20 之间的任何一个
因为未提交读的隔离级别下,其他事务对数据的修改也是对当前事务可见的
- 第二个
可能读到 1,10 和 20,他只能读到其他事务已经提交了的数据
- 第三个
读到的数据去决于自身事务开启的时间点
在事务开启时,读到的是多少,那么在事务提交之前读到的值就是多少
- 第四个
只有在 A end 到 B start 之间开启,才有可能读到数据,而在事务 A 和事务 B 执行的期间是读不到数据的
因为第四小人读数据是需要加锁的,事务 A 和 B 执行期间,会占用数据的写锁,导致第四个小人等待锁
不同隔离级别所面对的问题

隔离级别越高,所带来的资源消耗也就越大 (锁),因此它的并发性能越低
准确的说,在可串行化的隔离级别下,是没有并发的
MySql 中的事务
mysql 事务的实现基于存储引擎。不同存储引擎对事务的支持程度不一样
默认存储引擎innoDB,默认隔离级别 RR,并在 RR 隔离级别下更进一步
MVCC 解决不可重复读问题,间隙锁(并发控制)解决幻读问题
因此 innoDB 的 RR 隔离级别其实实现了串行化级别的效果,而且保留了比较好的并发性能
隔离性通过锁实现,原子性、一致性和持久性通过事务日志实现
事务日志,redo 和 undo log
redo log
innoDB 事务日志通过 redo log 和 InnoDB Log Buffer 实现
事务开启时,事务中的操作,先写入 Log Buffer,事务提交前,需要先刷盘持久化,这就是 Write-Ahead Logging
事务提交后,Buffer Pool 中映射的数据文件才会慢慢刷盘
如果数据库崩溃或宕机,系统重启进行恢复时,就可以根据 redo log 中记录的日志,把数据库恢复到崩溃前的一个状态
未完成的事务,可以继续提交,也可以选择回滚,基于恢复策略而定
Redo Log 顺序追加,改善 IO 性能
所有的事务共享 redo log 存储空间
记录 1:<trx1, insert...>
记录 2:<trx2, delete...>
记录 3:<trx3, update...>
记录 4:<trx1, update...>
记录 5:<trx3, insert...>
undo log
主要为事务的回滚服务
事务执行过程中,除记录 redo log,还会记录一定量的 undo log
undo log 记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据 undo log 进行回滚操作
单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作
undo+redo 事务的简化过程
假设有 2 个数值,分别为 A 和 B, 值为 1,2
- start transaction;
- 记录 A=1 到 undo log;
- update A = 3;
- 记录 A=3 到 redo log;
- 记录 B=2 到 undo log;
- update B = 4;
- 记录 B = 4 到 redo log;
- 将 redo log 刷新到磁盘
- commit
1-8 任意一步系统宕机,事务未提交,盘上数据无影响
8-9 间宕机,恢复之后可以选择回滚,也可以选择继续完成事务提交,因为此时 redo log 已经持久化
9 之后系统宕机,内存映射中变更的数据还来不及刷回磁盘,那么系统恢复之后,可以根据 redo log 把数据刷回磁盘
所以,redo log 保障持久性和一致性,undo log 保障了事务的原子性