幻读有什么问题,MySQL 是如何解决幻读的

快照读:读取快照中的数据,不需要进行加锁。看到快照这两个字,各位肯定马上就想到 MVCC 了,是这样,MVCC 作用于读取已提交和可重复读(默认)这两个隔离级别,这俩隔离级别下的普通 select 操作就是快照读

当前读:读取的是最新版本的数据, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。

除了读取已提交和可重复读这俩隔离级别下的普通 select 操作,其余操作都是当前读:

可重复读隔离级是由 MVCC(多版本并发控制)实现的,实现的方式是启动事务后,在执行第一个查询语句后,会创建一个视图,然后后续的查询语句都用这个视图,「快照读」读的就是这个视图的数据,视图你可以理解为版本数据,这样就使得每次查询的数据都是一样的。

MySQL 里除了普通查询是快照度,其他都是当前读,比如update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。

另外,select ... for update 这种查询语句是当前读,每次执行的时候都是读取最新的数据。

因此,要讨论「可重复读」隔离级别的幻读现象,是要建立在「当前读」的情况下。

幻读就是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
幻读的后果就是数据库中的数据和 binlog 的执行结果会不一致,其原因就在于,我们无法阻止新插入的数据。就是说,我们在给扫描到的行加锁的时候,你等会要插入的行还不存在,也就没法对他进行加锁,那么这个新插入的数据,可能在主库中是这个样子,从库执行完 binlog 后其实是会被修改的。
这也就是为啥幻读会被单独拎出来解决的原因了。
幻读问题在 "当前读" 下才会出现。
所谓当前读就是,读取的是最新版本的数据, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。
与之对应的,快照读,读取的是快照中的数据,不需要进行加锁。读取已提交和可重复读这俩隔离级别下的普通 select 操作就是快照读。其实就是 MVCC 机制,或者说,在快照读下,采用 MVCC 机制解决幻读。
然后,对于当前读这种情况,前面我们说,由于无法阻止新插入的数据,所以无法解决幻读问题,所以,我们考虑,不仅对扫描到的行进行加锁,还对行之间的间隙进行加锁,这样就能杜绝新数据的插入和更新。这个其实就是记录锁 Record Lock 和间隙锁 Gap Lock,也被称为临键锁 Next-Lock Key。
额临键锁只在可重复读也就是 InnoDB 的默认隔离级别下生效。也可以采用更高的可串行化隔离级别,所有的操作都是串行执行的,可以直接杜绝幻读问题。

现在你知道了,产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,操作的是锁住的行之间的 “间隙”。因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。

这样,当你执行 select * from user where name = 'Jack' for update 的时候,就不止是给数据库中已有的 n 个记录加上了行锁,还同时加了 n + 1 个间隙锁(这两个合起来也成为 Next-Key Lock 临键锁)。也就是说,在数据库一行行扫描的过程中,不仅扫描到的行加上了行锁,还给行两边的空隙也加上了锁。这样就确保了无法再插入新的记录。

总结下 MySQL 解决幻读的手段:

隔离级别:可重复读

  • 快照读 MVCC + 当前读 Next-Lock Key(只在可重复读隔离级别下生效)

隔离级别:SERIALIZABLE

  • 在这个隔离级别下,事务在读操作时,先加表级别的共享锁,直到事务结束才释放;事务在写操作时,先加表级别的排它锁,直到事务结束才释放。也就是说,串行化锁定了整张表,幻读不存在的


posted @   huigui_mint  阅读(87)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示