【MySQL 事务并发实战】:隔离级别、MVCC 与幻读问题彻底解

🔥你好我是fengxin_rou这是我的个人主页 fengxin_rou的主页

❄️欢迎查看我的专栏我的专栏

《Java后端学习》《JAVASE基础》《JUC并发》《redis》《JVM虚拟机》《MYSQL》《黑马点评》《rabbitmq》《JavaWeb+AI的talis学习系统》《苍穹外卖》

上一期MySQL 事务四大特性与并发问题全解析:脏读、不可重复读、幻读彻底搞懂讲解了MYSQL在高并发下出现的问题,那么这一期主要讲解一下如何处理在高并发的情况下解决这些问题

前言:

上一篇我们拆解了 MySQL 事务在高并发下的三大典型问题:脏读、不可重复读与幻读,很多同学看完后留言问:"这些问题在 MySQL 中到底是怎么被解决的?可重复读明明是默认隔离级别,为什么还是会出现幻读?"

这篇就来深入聊聊 MySQL 并发控制的底层方案:从锁机制、事务隔离级别,到 MVCC 多版本并发控制,再到可重复读下幻读的真实成因,帮你彻底搞懂 MySQL 是如何平衡 "数据一致性" 与 "并发性能" 的。

目录

前言:

一、mysql的是怎么解决并发问题的?

二事务的隔离级别有哪些?

三、可重复读隔离级别下,A事务提交的数据,在B事务能看见吗?

四、为什么在可重复读的级别下还会出现幻读?

场景:

总结


一、mysql的是怎么解决并发问题的?

锁机制:mysql提供了两种锁机制,即行级锁和表级锁,这两种锁在常见的存储引擎中的InnoDB和MyISAM都支持,在网上说的页级锁,是支持于已经废弃的BDB存储引擎,并且InnoDB和MyISAM都不支持页级锁,通过对锁机制就可以保证只有一个事务操作数据库,并且保证数据的一致性

事务隔离级别: mysql对事务管理有不同的事务隔离级别,读未提交、读已提交、可重复读和串行化,通过不同隔离级别的控制,可以在不同事务并发执行的时候,避免数据的不一致问题

MVCC(多版本并发控制): mysql提供MVCC管理mysql不同版本的数据,通过保存不同版本的数据来实现事务的隔离 ,在读操作时,通过不同的隔离级别来选择合适的数据版本,从而保证数据的一致性

二事务的隔离级别有哪些?

读未提交(read uncommitted) :在事务还没有结束时 ,其他事务就可以看到该事务的变更

读已提交(read committed) :在事务已经提交后 ,其他时候才能看到事务做的变更

可重复读(repeatable read) :事务执行过程中 读取的数据和一直一开始 启动事务时读取的是数据一致

串行化(serializable) :在读写一个数据的时候会给这个数据上读写锁 ,如果多个事务出现了读写冲突,这个数据只有等前一个事务执行完之后才能操作

读写冲突指「一个事务在读,另一个事务在写同一条数据」
读未提交:脏读、幻读、不可重复读都有可能发生

读已提交:脏读不可能发生,另外二者都可能发生

可重复读 :脏读和不可重复读不可能发生,幻读可能发生(是mysql的默认隔离级别)

因为MVCC 无法阻止事务启动后新增的提交数据 → "当前读"操作会看到新数据,所以依然存在。

**串行化:**三者都不可能发生

举例说明:事务A查询余额,事务B更改余额为200w,看不同事务的隔离情况

  • 在「读未提交」隔离级别下,事务 B 修改余额后,虽然没有提交事务,但是此时的余额已经可以被事务 A 看见了,于是事务 A 中余额 V1 查询的值是 200 万,余额 V2、V3 自然也是 200 万了;
  • 在「读提交」隔离级别下,事务 B 修改余额后,因为没有提交事务,所以事务 A 中余额 V1 的值还是 100 万,等事务 B 提交完后,最新的余额数据才能被事务 A 看见,因此额 V2、V3 都是 200 万;
  • 在「可重复读」隔离级别下,事务 A 只能看见启动事务时的数据,所以余额 V1、余额 V2 的值都是 100 万,当事务 A 提交事务后,就能看见最新的余额数据了,所以余额 V3 的值是 200 万;
  • 在「串行化」隔离级别下,事务 B 在执行将余额 100 万修改为 200 万时,由于此前事务 A 执行了读操作,这样就发生了读写冲突,于是就会被锁住,直到事务 A 提交后,事务 B 才可以继续执行,所以从 A 的角度看,余额 V1、V2 的值是 100 万,余额 V3 的值是 200万。

三、可重复读隔离级别下,A事务提交的数据,在B事务能看见吗?

答案是看不见

因为可重复读是由MVCC(多版本并发控制)实现的,在事务开启后,执行第一个查询之后会创建一个Read View,通过这个Read View后续都可以通过undo log版本来查询到最开始的数据,所以在这种情况下其他事务提交的数据时看不见的

四、为什么在可重复读的级别下还会出现幻读?

InnoDB RR 可重复读下,单纯只做「快照读」,永远不会幻读;

只有事务 A 里同时出现「快照读 + 当前读」混用,才会产生幻读。

这里要区分什么是快照读、当前读:

  • 普通的SELECT(不加锁)是快照读 ,严格遵循Read View规则,只能看到事务启动时的可见数据。
  • INSERTUPDATEDELETESELECT ... FOR UPDATE这些写操作 / 加锁读,都是当前读 ,它们会直接读取数据的最新版本,不遵循事务 A 的Read View

场景:

t 初始数据:id=1id=2 事务 A:RR 级别,查询并更新 id between 1 and 10 的数据 事务 B:插入 id=3 并提交

表格

步骤 事务 A(RR) 事务 B 关键行为
1 开启事务,生成Read View - 此时表里只有 id=1、2,Read View 记录活跃事务 ID
2 SELECT * FROM t WHERE id BETWEEN 1 AND 10; → 查到 2 条 - 快照读,遵循 Read View,看不到未来数据
3 - 开启事务,INSERT INTO t(id) VALUES(3); COMMIT; 事务 B 提交,id=3 的数据写入磁盘,事务 ID 比 A 的大
4 UPDATE t SET name='test' WHERE id BETWEEN 1 AND 10; - 当前读!直接读取最新数据,看到了 id=3,对它也做了更新
5 SELECT * FROM t WHERE id BETWEEN 1 AND 10; - 快照读,还是只看到 2 条

这时候,矛盾就出现了:

  • 事务 A 自己更新了 id=3,但快照读却看不到它,逻辑上就出现了 "幻觉"。
  • 当事务 A 后续再做一次当前读(比如再执行一次UPDATESELECT ... FOR UPDATE),就会发现 id=3 这条数据,前后两次查询的结果不一致,这就是幻读。

总结

MySQL 依靠锁机制、事务隔离级别、MVCC 解决高并发数据不一致问题。锁分为表级锁与行级锁,保障并发操作互斥;四种隔离级别逐级规避脏读、不可重复读、幻读,隔离级别越高一致性越好、性能越低。

可重复读是 MySQL 默认隔离级别,基于 MVCC 和 ReadView 实现,事务启动后固定数据快照,看不到其他事务已提交数据,天然避免脏读和不可重复读。

普通普通 SELECT 属于快照读,遵循 ReadView;增删改、加锁查询是当前读,读取最新数据。RR 下纯快照读无幻读,只有快照读与当前读混用时,能更新到新增数据却查询不到,从而产生幻读。

相关推荐
白开水就盒饭4 小时前
《数据挖掘(主编:吕欣、王梦宁)》读书笔记总结
python·mysql·数据挖掘·知识图谱
bqq198610264 小时前
非关系型数据库概述
数据结构·数据库·非关系型数据库
@insist12313 小时前
信息安全工程师-数据库安全全体系解析与最佳实践
数据库·安全·软考·信息安全工程师·软件水平考试
_ku_ku_14 小时前
数据库系统原理 · 事务管理与恢复 · 自学总结
数据库·oracle
lifewange15 小时前
Redis 集合(Set)运算完全指南
数据库·chrome·redis
TDengine (老段)15 小时前
TDengine RAFT共识协议 — 选举、日志复制、快照与仲裁
android·大数据·数据库·物联网·架构·时序数据库·tdengine
Full Stack Developme16 小时前
Spring Boot 事务管理完整教程
java·数据库·spring boot
m0_7020365318 小时前
mysql如何通过索引减少行锁范围_mysql索引与加锁逻辑
jvm·数据库·python
qxwlcsdn18 小时前
如何用 IndexedDB 存储从 API 获取的超大列表并实现二级索引
jvm·数据库·python