MySQL 的幻读与 MVCC,锁

2025/01/22 database 共 1385 字,约 4 分钟

MySQL 的幻读与 MVCC,锁

太长不看版本总结:

MySQL 默认事务隔离级别是 RR 可重复读,没有完全防止幻读。

MySQL 分为快照读和当前读,两种读分别在各自情况下防止了幻读,快照读使用快照,不会幻读,当前读用 record lock,gap lock,next-key lock 三种锁,将记录与范围按需要锁住(常被笼统的一起称为行锁),隔绝了其他事务的影响,防止了幻读。

两种读之间切换会出现幻读。

前言

我再次学习数据库关于事务隔离级别时,看到两方面关于 MySQL 防止幻读的方法,MVCC 与 三种锁。网上关于 MySQL 防止幻读这个问题讲的很混乱,太过注重实现细节,没有人拿出来对比着说清楚,还有似是而非的错误说法,比如:

MySQL的默认事务隔离级别是可重复读(REPEATABLE READ)。这意味着在一个事务中,多次读取同样的记录结果是一致的,除非数据被当前事务自己修改。这个级别适用于大多数应用场景,因为它提供了较好的并发性能,同时也能避免脏读和不可重复读的问题。

这意味着在一个事务中,多次读取同样的记录结果是一致的,除非数据被当前事务自己修改。这句话是错误的,是缺少前提的,这里我重新总结一下。

测试

版本

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.6.51    |
+-----------+
1 row in set (0.00 sec)
mysql> SELECT @@SESSION.TX_ISOLATION;
+------------------------+
| @@SESSION.TX_ISOLATION |
+------------------------+
| REPEATABLE-READ        |
+------------------------+
1 row in set (0.00 sec)

过程

事务 A

start transaction ;
select * from o_bill where id>=400791;

事务 B

start transaction;
insert into o_bill (id) value (400793);
commit;

此时,事务 B 成功执行并提交。

事务 A

select * from o_bill where id>=400791;

看不到事务 B 刚刚插入的 400793。很好理解,在快照读。

那么执行下面会获得什么呢?

select * from o_bill where id>=400791 for update;

会看到事务 B 刚刚插入的 400793,此时发生了幻读

有意思的来了,再执行下面的会怎么样呢?

select * from o_bill where id>=400791;

又切换到快照读,又看不到 400793了。很神秘,还能切回去。

总结

其实这些现象可以说是使用快照,保证可重复读,增加并发性能造成的结果。这会造成什么影响呢:在事务中,如果你使用 select(快照读),再进行 update/delete(切换当前读),你可能会变更与当前事务第一步 select 出的不同的数据。

因此使用时,我们应当区别单纯的读事务,此时使用不加锁的快照读,使用 select,以及可能存在更新的事务,使用会加锁的当前读,使用 select for update。这种解决方案也会有更多潜在的影响数据库吞吐量的影响,需要根据情况和场景进行进一步的评估。

文档信息

Search

    Table of Contents