# 1 事务
事务是由单独的一个或者多个 SQL 语句组成,是最小的不可再分割的单元,这一组里的所有操作,要么全部执行成功,要么全部执行失败。如果有一个执行不成功,则全部回滚。
例如,去银行转账,A 给 B 转账 100 元,此时 A 的账户应该扣除 100 元,B 的账户应该增加 100 元,这两个操作必须全部成功完成,如果有一个操作出现失败,则必须进行回滚。
# 2 事务的并发问题
# 2.1 脏读
事务 A 读取到了事务 B 已经修改还未提交的数据,此时如果事务 B 回滚,则事务 A 读取到的数据就是无效的,这种数据就是 "脏数据",事务 A 根据这个数据进行操作可能就会出现错误。
# 2.2 不可重复读
事务 A 读取某一个字段时,事务 B 对该字段进行了修改,事务 A 再次读取该字段时,读取到的值和第一次读取到的值不一致。也就是在事务 A 两次读取的间歇时,事务 B 对数据进行了修改,导致两次读取的数据不一致。
# 2.3 幻读
事务 A 查询表中数据,事务 B 新增了数据(insert/delete)到该表中,事务 A 再次查询表中数据,两次查询结果不一致。
不可重复读和幻读的区别:不可重复读针对的是修改,幻读针对的是新增或删除。
不可重复读指的是两次读取过来的数据内容不一样,幻读指的是两次读取过来的数据条数不一样。幻读强调的是集合的增减,而不是单条数据的更新。
# 3 事务的隔离级别
# 3.1 读取未提交(Read Uncommitted)
允许事务读取到其他事务未提交的数据。事务 A 可以读取到事务 B 修改但是未提交的数据。隔离级别最低,上面出现的并发问题都没解决。
# 3.2 读取已提交(Read Committed)(Oracle 默认)
事务只能看到其他事务已经提交的数据。事务 A 只能读取事务 B 已经提交的数据。可以避免脏读,但是不可以避免不可重复读和幻读。
# 3.3 可重复读(Repeatable Read)(MySQL 默认)
事务多次读取时可以保证读取到相同的值,在事务期间,禁止其他事务对该数据进行更新操作(update, 不包括 insert 和 delete)。事务 A 读取某商品数量 100,在此期间禁止事务 B 更新该商品数量,事务 A 再次读取该商品数量还是 100. 可以避免脏读和不可重复读,但是无法避免幻读。
# 3.4 串行化(Serializable)
证事务读取到相同的数据,在事务期间,禁止其他事务对该表进行新增、更新、删除操作。不存在任何并发。
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
- 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁 + 间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
# 参考
美团三面:一直追问我, MySQL 幻读被彻底解决了吗? - 知乎 (zhihu.com)