阅读之前,请心里默念,脏读、不可重复读、幻读是三种常见的并发问题,隔离级别是应对并发问题的四种隔离级别,隔离级别和并发问题是两个东西,不要混淆。
在数据库事务中,脏读(Dirty Read)、不可重复读(Non-Repeatable Read) 和 幻读(Phantom Read) 是三种常见的并发问题。它们与事务的隔离级别密切相关,理解这些问题有助于选择合适的隔离级别来平衡数据一致性和并发性能。
一、三大并发问题
1、脏读
脏读是指一个事务读取了另一个未提交事务的修改数据。如果未提交事务最终回滚,那么读取到的数据就是无效的(即”脏数据“)
示例:
假设有两个事务事务A和事务B:
-
事务A更新了一行数据,将balance从100改为200,但尚未提交
-
事务B读取了这行数据,得到balance=200
-
事务A回滚了更新操作,balance恢复为100
-
事务B读取到了balance=200是无效的(脏数据)
影响
-
数据不一致:事务读取到的数据可能是无效的
-
业务逻辑错误:基于脏数据的操作可能导致错误的业务结果
解决方式
使用读已提交或更高的隔离级别,确保事务只能读取已提交的数据
2、不可重复读
不可重复读是指在一个事务中,多次读取同一数据时,由于其他事务的修改,导致读取到的结果不一致
示例:
假设有两个事务事务A和事务B
-
事务A读取了一行数据,得到balance=100.
-
事务B更新了这行数据吗,将balance从100改为200,并提交
-
事务A再次读取这行数据,得到balance=200
-
事务A在同一个事务中两次读取同一数据,结果不一致
影响:
-
数据不一致:事务中多次读取同一数据可能得到不同结果
-
业务逻辑错误:基于不一致数据的操作可能导致错误的业务结果
解决方式
-
使用可重复读或更高的隔离级别,确保事务中多次读取同一数据时结果保持一致
3、幻读
幻读是指在一个事务中,多次执行相同的查询时,由于其他事务的插入或删除操作,导致返回的结果集不一致
示例:
假设有两个事务事务A和事务B
-
事务A执行查询
SELECT * FROM users WHERE age>18
,返回10行数据 -
事务B插入了一行新数据,age=20,并提交
-
事务A再次执行相同的查询,返回11行数据
-
事务A在同一个事务中两次执行相同的查询,结果集不一致(新增了一行数据)
影响
-
数据不一致:事务中多次执行相同的查询可能返回不同的结果集
-
业务逻辑错误:基于不一致结果集的操作可能导致错误的业务结果
解决方式
-
使用串行化(Serializable)隔离级别,通过枷锁机制确保事务串行化执行,避免幻读
-
在MySQL中,可重复读(Repeatable Read)隔离级别通过Next-key Lock机制部分解决了幻读问题
4、不可重复读和幻读的区别
不可重复读是指在一个事务中,多次读取同一行数据时,由于其他事务的修改(更新或删除),导致读取到的结果不一致
幻读是指在一个事务中,多次执行相同的查询时,由于其他事务的插入或删除操作,导致返回的结果集不一致
不可重复读示例
-
事务A 读取
balance = 100
。 -
事务B 更新
balance = 200
并提交。 -
事务A 再次读取
balance
,发现值变为 200。
幻读示例
-
事务A 执行一个范围查询(如
SELECT * FROM users WHERE age > 18
)。 -
事务B 插入或删除了符合该查询条件的数据并提交。
-
事务A 再次执行相同的查询时,发现结果集发生了变化(新增或减少了行)。
特性 | 不可重复读(Non-Repeatable Read) | 幻读(Phantom Read) |
---|---|---|
操作对象 | 同一行数据 | 多行数据(结果集) |
触发操作 | 更新(UPDATE)或删除(DELETE) | 插入(INSERT)或删除(DELETE) |
影响范围 | 单行数据的值发生变化 | 结果集的行数发生变化 |
示例 | 事务A 读取 balance = 100 ,事务B 更新为 200。 | 事务A 查询 age > 18 ,事务B 插入新数据。 |
两个并发问题的关注点在于(一行数据和结果集)
二、四种隔离级别
1. 读未提交(Read Uncommitted)
事务可以读取其他未提交事务的修改(即“脏读”)。
特点
-
最低的隔离级别,提供最少的并发控制。
-
事务可以看到其他事务未提交的数据,可能导致脏读、不可重复读和幻读。
-
性能最高,但数据一致性最差。
问题
-
脏读(Dirty Read):事务读取到其他未提交事务的修改,如果该事务回滚,读取的数据就是无效的。
使用场景
-
对数据一致性要求极低的场景,如统计查询等。
2. 读已提交(Read Committed)
事务只能读取其他事务已经提交的数据。
特点
-
解决了脏读问题,但可能出现不可重复读和幻读。
-
每次执行查询时都会生成一个新的快照(Read View),确保事务只能看到已经提交的数据。
-
性能较高,数据一致性较好。
问题
-
不可重复读(Non-Repeatable Read):同一事务中多次读取同一数据,结果不一致。
-
幻读(Phantom Read):同一事务中多次执行相同的查询,结果集不一致。
使用场景
-
对数据一致性有一定要求,但允许不可重复读的场景。
3. 可重复读(Repeatable Read)
事务在执行期间多次读取同一数据时,结果是一致的。
特点
-
解决了脏读和不可重复读问题,但可能出现幻读。
-
事务在第一次执行查询时生成一个快照(Read View),并在整个事务期间使用该视图。
-
MySQL 的默认隔离级别。
-
性能中等,数据一致性较好。
问题
-
幻读(Phantom Read):同一事务中多次执行相同的查询,结果集不一致。
使用场景
-
对数据一致性要求较高的场景,如金融系统等。
4. 串行化(Serializable)
事务串行执行,完全隔离,确保最高的数据一致性。
特点
-
最高的隔离级别,提供最强的并发控制。
-
通过加锁机制(如读写锁)确保事务串行执行。
-
解决了脏读、不可重复读和幻读问题。
-
性能最低,数据一致性最强。
问题
-
性能开销大:由于事务串行执行,并发性能大幅下降。
-
锁冲突:可能导致大量锁等待和死锁。
使用场景
-
对数据一致性要求极高的场景,如银行核心系统等。
5. 隔离级别对比
隔离级别 | 脏读(Dirty Read) | 不可重复读(Non-Repeatable Read) | 幻读(Phantom Read) | 性能 |
---|---|---|---|---|
读未提交 | 可能 | 可能 | 可能 | 最高 |
读已提交 | 不可能 | 可能 | 可能 | 较高 |
可重复读 | 不可能 | 不可能 | 可能 | 中等 |
串行化 | 不可能 | 不可能 | 不可能 | 最低 |
但是
一般来说,面试的话面试官会转手就问你,那mysql是怎么实现隔离的呢?或者问你对数据库的MVCC(多版本并发控制了解多少)? 请参考 MySQL隔离级别的实现方式
然后就又问你,如果用可重复读,我们基于第一次查询到100块,在事务未提交前,第二个事务将100更新为150,这个时候基于第一次的100去操作数据,结果是错的,这时该怎么办?请参考
当规避了不可重复读问题后,怎么保证幂等性
那么又有个问题,既然都从头到尾只读第一次的数据了,为什么可重复读不能决幻读问题呢?可重复读隔离级别为什么不能防止幻读