并发事务~
一、并发事务出现的场景大致可以划分为以下 3 种:
1、读-读:即并发事务相继读取相同的记录。
读取操作本身不会对记录有一毛钱影响,并不会引起什么问题,所以允许这种情况的发生。
2、写-写:即并发事务相继对相同的记录做出改动。
我们前边说过,在这种情况下会发生脏写的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行,这个排队的过程其实是通过锁来实现的。
3、读-写:即一个事务进行读取操作,另一个进行改动操作。
这种情况下,主要可能导致以下三类问题(严重性递增):
- 脏读 (Dirty Read):
- 问题描述:一个事务 (T1) 读取了另一个未提交事务 (T2) 修改的数据。随后,T2 可能会回滚(撤销修改),导致 T1 读取的数据实际上从未在数据库中有效存在(即“脏”数据)。
- 危害:T1 基于无效的数据进行了操作,可能导致业务逻辑错误。
- 示例:T2 更新账户 A 余额为 +100(但尚未提交);T1 读取账户 A 余额为 +100;T2 因为某种原因回滚,账户 A 实际余额未变;T1 基于读到的 +100 余额进行操作出错。
- 不可重复读 (Non-Repeatable Read):
- 问题描述:在同一个事务 (T1) 中,多次读取同一行数据(例如SELECT ... WHERE id=1),在 T1 未结束期间,另一个事务 (T2)提交了对该行数据的修改(UPDATE 或 DELETE),导致 T1 前后两次读取的结果不一致。
- 危害:破坏了一个事务内逻辑一致性的预期。在一个事务里读取同一个东西,值变了。
- 示例:T1 第一次查询账户 A 余额为 500;此时 T2 提交了对账户 A 的更新,余额变为 400;T1 第二次查询账户 A 余额,发现变成 400 了,与第一次不一致。
- 幻读 (Phantom Read):
- 问题描述:在同一个事务 (T1) 中,多次执行相同的范围查询(例如SELECT ... WHERE value > 100),在 T1 未结束期间,另一个事务 (T2)提交并插入 (INSERT) 或删除 (DELETE) 了某些符合该查询条件范围的数据行,导致 T1 前后两次查询得到的行数或结果集发生了变化(即使同一个 id 的值没变)。
- 危害:破坏了一个事务内逻辑一致性的预期。范围查询的结果集在事务过程中“凭空”多出了或消失了行,如同幻影。
- 示例:T1 查询所有余额大于 100 的账户,得到 5 条记录;此时 T2 提交了一个新账户的 INSERT,该账户余额为 200;T1 再次查询余额大于 100 的账户,得到 6 条记录。或者 T2 删除了一条记录,第二次查询返回更少的行。
重要区别:
- 脏读 vs 不可重复读:脏读读取的是未提交的数据;不可重复读读取的是已提交的数据,但同一个行记录的值在事务过程中被其他事务修改了。
- 不可重复读 vs 幻读:不可重复读关注的是同一个行记录的值是否被修改或删除;幻读关注的是满足查询条件的行数/结果集是否因插入或删除操作而改变(新的、之前不存在的行“出现”或“消失”)。
二、SQL 标准事务隔离级别及其解决的问题
为了解决读写冲突导致的问题,SQL 标准定义了四种隔离级别,隔离性由低到高,解决的问题也逐步增多:
- 读未提交 (Read Uncommitted):
- 最低隔离级别。
- 可能出现的问题:脏读、不可重复读、幻读。
- 解决问题:无。它完全不做任何并发控制(除了可能防止某些最极端的损坏如更新丢失),性能最高但数据一致性风险最大。一般不推荐使用。
- 读已提交 (Read Committed):
- 大部分主流数据库的默认隔离级别(如 Oracle, PostgreSQL, SQL Server)。注意:MySQL InnoDB 默认是 Repeatable Read,这与标准不同。
- 可能出现的问题:不可重复读、幻读。
- 解决问题:脏读。
- 原理简述:一个事务只能读取到其他事务已经提交的修改。通常通过在每次 SELECT 时获取一个短暂的读锁(或者通过 MVCC,读取当前已提交的最新快照)来实现,读取完成后立即释放锁。
- 可重复读 (Repeatable Read):
- MySQL InnoDB 存储引擎的默认隔离级别。
- 可能出现的问题:
- SQL 标准定义:幻读。
- MySQL InnoDB 的实际实现:通过 MVCC 和 Next-Key Locking 机制,基本避免了幻读。这在 MySQL 中是一个重要的特性和优化。
- 解决问题:脏读、不可重复读(并且在 MySQL InnoDB 中,基本解决了幻读)。
- 原理简述:在整个事务执行过程中,第一次读取某行数据时会建立一个快照(MVCC),后续对该行的读取都基于这个快照,其他事务提交的 UPDATE 对该事务不可见。同时,为了防止新行插入造成的幻读,会对查询涉及的范围加间隙锁 (Gap Lock) 或 Next-Key Lock。但已提交的其他事务的 INSERT 可能影响该事务中后续的 INSERT/UPDATE 操作(数据行冲突)。
- 串行化 (Serializable):
- 最高隔离级别。
- 可能出现的问题:无(完全隔离,所有问题都解决)。
- 解决问题:脏读、不可重复读、幻读。
- 原理简述:通过强制所有事务串行执行来实现(类似于单线程)。具体实现可能是在 SELECT 读取的范围自动加上共享锁 (Shared Lock),或者在涉及写入的冲突时通过锁竞争强制串行。性能开销最大。
三、隔离级别与问题总结表
也就是说:
- READ UNCOMMITTED隔离级别下,可能发生脏读、不可重复读和幻读问题。
- READ COMMITTED隔离级别下,可能发生不可重复读和幻读问题,但是不可以发生脏读问题。
- REPEATABLE READ隔离级别下,可能发生幻读问题,但是不可以发生脏读和不可重复读的问题。
- SERIALIZABLE隔离级别下,各种问题都不可以发生。