事务必须遵循四个特性:
- 原子性:一个事务中的所有操作,要么全部完成,要么全部不完成
- 一致性:事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态
- 隔离性:数据库允许多个并发事务同事对其数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由于交叉执行导致的数据不一致。
- 持久性:事务处理结束后,对数据的修改就是永久的,即使系统故障也不会丢失
InnoDB 引擎通过以下方式实现这四个特性:
- 通过 redo log 保证持久性
- 通过 undo log 保证原子性
- 通过 MVCC 多版本并发控制或锁机制保证隔离性
- 通过以上三种方式保证一致性
隔离性
并行事务会有什么问题?
在处理多个事务的时候,可能会出现脏读、幻读、不可重复读的问题
脏读
如果一个事务读到了另一个未提交事务修改过的数据,这就意味着发生了脏读
举例来说,如果有 A 和 B 另两个事务同时在处理用户的余额数据:
- 两个事务未做修改时,用户的余额为 1000 元
- A 事务对用户余额做了修改,添加了 200 元
- B 事务读到的用户余额为 1200 元,此时发生了脏读
由于 A 事务还未提交,因此有可能发生回滚。在 A 事务回滚之后,B 事务读到的 1200 元时无效数据,这种现象就被称为脏读
不可重复读
在一个事务中多次读取同一个数据,如果出现前后两次独到的数据不一致的情况,就意味着发生了「不可重复读」
举例来说,如果 A 事务读取了用户的余额数据为 1000,如果有其他事务修改并提交了余额,A 事务再次读取,得到的余额数据为 1200,这两次的数据是不一致的,这种现象称为不可重复读
注意,「不可重复读」与「脏读」是有区别的:
- 脏读读到的是其他事务未提交的数据
- 不可重复读读到的其他事务已提交的数据
幻读
在一个事务里多次查询某个符合条件的「记录数量」,如果出现了前后两次查询到的记录数量不一致的情况,就意味着发生了幻读
举例来说,如果有 A 事务查询用户余额超过 100 万 的数据有 5 条,之后有其他事务对数据进行了插入或者删除,A 事务再次查询数据,得到的记录数量不等于 5
隔离级别
SQL 标准提出了四种隔离级别来规避以上三种问题,隔离级别越高,性能效率就越低:
- 读未提交(read uncommitted):指一个事务还没提交时,它做的变更就能被其他事务看到
- 读提交(read committed):指一个事务提交之后,它做的变更才能被其他事务看到
- 可重复读(repeatable read):指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。这是 InnoDB 的默认隔离级别
- 串行化(serializable):会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突,后访问的事务必须等前一个事务执行完成才能继续执行
事务的隔离级别排序为:串行化 > 可重复度 > 读提交 > 读未提交
- 在读未提交级别下,可能发生脏读、不可重复读、幻读
- 在读提交级别下,可能大声不可重复度、幻读
- 在可重复度级别下,可能发生幻读
- 在串行化级别下,可规避这三个问题
MySQL InnoDB 引擎的默认级别虽然是可重复读,但它在很大程度上避免了幻读现象(并未完全解决):
- 针对快照读(普通 select),通过 MVCC 方式解决了幻读。在可重复读级别下
- 针对当前读(select for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读