1.mysql的四种隔离级别
- 读未提交(Read Uncommitted) :一个事务能看到另一个未提交的修改。
问题:脏读(读取到未提交的 “临时数据”)。 - 读已提交(Read Committed,RC) :一个事务只能看到另一个已提交的修改。
解决:脏读;问题:不可重复读(同一事务中多次读取结果不一致,因其他事务提交了修改)。 - 可重复读(Repeatable Read,RR) :同一事务中多次读取结果一致,不受其他事务提交的修改影响。
解决:脏读、不可重复读;问题:幻读(同一事务中,新增 / 删除数据导致前后查询行数不一致)。 - 串行化(Serializable) :事务完全串行执行(不并发),隔离性最高。
解决:所有问题;问题:性能极差,适合并发量极低的场景。
2.幻读的情况
幻读通俗易懂的讲就是:同一事务中,两次执行相同的查询,结果集的 “行数” 莫名其妙地变了(多了或少了),就像出现了幻觉一样。
可重复读实际上是解决了同一个事务中,同一条数据多次读结果一致
重点:行数。
此处有两个场景:快照读、当前读
快照读:通过MVCC可以解决幻读。MVCC(多版本并发控制)来实现数据一致性的,会在第一次执行时生成一个 ReadView,后续查询都基于这个 ReadView 来获取数据,从而避免了幻读现象。
如:普通的SELECT语句(不加FOR UPDATE/LOCK IN SHARE MODE),通过 MVCC 读取数据的历史版本(快照),不会加锁。
当前读:需要结合间隙锁去解决幻读。
如:需要读取最新数据的操作(如SELECT...FOR UPDATE、UPDATE、DELETE、INSERT),必须读取当前最新的记录,会加锁(可能是行锁、间隙锁等)。
注意区分:快照读是读取不到最新数据的,当前读才可以。
3.间隙锁
在 InnoDB 存储引擎中,通常有索引的查询才会触发间隙锁,并且在可重复读隔离级别下,间隙锁可以解决幻读问题。
如果是普通的SELECT...语句,没有使用FOR UPDATE或LOCK IN SHARE MODE等锁定读语句,在可重复读隔离级别下是不会自动加上间隙锁的。普通的SELECT语句属于快照读,它是通过 MVCC(多版本并发控制)来实现数据一致性的,会在第一次执行时生成一个 ReadView,后续查询都基于这个 ReadView 来获取数据,从而避免了幻读现象,而不需要加间隙锁。
只有当执行SELECT...FOR UPDATE或SELECT...LOCK IN SHARE MODE等当前读语句时,在可重复读隔离级别下,如果查询条件使用了索引(包括范围查询、非唯一索引的等值查询等情况),才会根据情况自动加上间隙锁或临键锁(间隙锁和记录锁的组合),以防止其他事务在查询范围内插入或删除数据,进而避免幻读。
4.总结
-
MVCC(快照读) :解决 “只查询不修改” 场景的幻读,通过历史快照保证一致性,无锁开销,性能好。
-
间隙锁(当前读) :解决 “查询后需要修改” 场景的幻读,通过锁定范围确保最新数据的一致性,保证修改操作的准确性(如订票、库存扣减)。
简单说:看数据时用 MVCC(快照读),改数据时用间隙锁(当前读) ,两者针对不同场景,缺一不可。