MySQL8四大事务隔离级别详解,彻底搞懂脏读、不可重复读、幻读
MySQL8四大事务隔离级别详解,彻底搞懂脏读、不可重复读、幻读
做后端开发久了,我相信大家都碰到过一类特别头疼的线上疑难问题:
代码逻辑反复核对没有问题,单元测试全部通过,测试环境稳得一批。可一旦上线生产,就随机出现库存莫名超卖、用户余额对账不平、订单数据错乱的问题。
很多人排查日志、调试代码、优化SQL折腾一整天,完全找不到问题根源,最后只能无奈归结为:数据库偶发抽风。
其实从业这么多年,我可以很明确的说:绝大多数随机的数据不一致问题,根本不是代码bug,而是你没吃透 MySQL 事务隔离级别。
事务隔离级别,算是 MySQL 最基础、但也最容易被轻视的核心知识点。不管是面试必考,还是线上问题排查,都绕不开它。但大部分开发者都只是死记硬背概念,分不清几种隔离级别的实际差异,更不知道不同业务该如何选型,遇到数据错乱问题完全无从下手。
今天我结合自己线上真实踩坑经历,抛开枯燥的教科书话术,用通俗的大白话、可直接复制运行的SQL案例,手把手带大家彻底读懂四大事务隔离级别,搞定脏读、不可重复读、幻读这三个高频难题。
内容偏向实战落地,建议收藏,以后面试、线上排查问题都用得上。
一、到底什么是事务隔离?
熟悉数据库的朋友都知道,事务拥有经典的 ACID 四大特性:原子性、一致性、隔离性、持久性。
在这四个特性里,隔离性是专门用来解决数据库并发读写问题的关键。
我们的线上系统,同一时刻会涌入大量请求,无数事务同时读写同一张表、同一条数据。如果事务之间没有任何隔离机制,所有读写操作互相干扰,数据错乱、数据覆盖的问题会遍地都是。
简单来讲,事务隔离级别,就是用来约束并发事务的读写可见规则,控制不同事务之间能不能互相读取对方的数据。
这里有个大家一定要记住的取舍逻辑:隔离级别越高,数据一致性越强,线上bug越少,但数据库并发性能越差;反之,隔离级别越低,并发吞吐越高,但数据出错的风险会大幅提升。
(插图建议:通俗易懂示意图——事务隔离级别「性能与安全性取舍关系图」)
二、并发事务三大数据异常:脏读、不可重复读、幻读
想要学懂隔离级别,首先得搞明白并发场景下的三类数据异常。说白了,MySQL 设计四大隔离级别,唯一目的就是针对性解决这三个问题。
全文案例统一复用下面这张测试表,大家可以直接复制语句建表测试,跟着实操更容易理解:
CREATETABLE`goods_stock`(`id`intPRIMARYKEYAUTO_INCREMENT,`goods_name`varchar(50)NOTNULLCOMMENT'商品名称',`stock`intNOTNULLDEFAULT0COMMENT'库存数量')ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;-- 插入测试数据INSERTINTOgoods_stock(goods_name,stock)VALUES('夏季短袖',100);1、脏读:读取到了无效的作废数据
通俗解释:一个事务读取到了其他事务尚未提交的数据,如果对方后续回滚事务,当前读取到的数据就彻底失效,变成了毫无意义的脏数据。
业务场景举例:事务A查询商品库存为100,此时事务B修改库存为80,但没有提交。事务A读取到了80的库存并执行业务逻辑。后续事务B因为异常回滚,库存恢复为100,这就直接导致事务A基于错误数据完成业务,出现数据错乱。
核心特征:读取未提交、随时会作废的数据,风险最高。
2、不可重复读:同一事务,两次查询结果不一致
通俗解释:在同一个事务生命周期内,两次查询同一条数据,期间被其他事务修改并提交,导致前后查询结果不一致。
业务场景举例:事务A开启事务,第一次查询库存是100。紧接着事务B扣减库存至90并成功提交。事务A再次查询同一条数据,库存变为90。同一个事务内数据变动,很容易造成对账、统计类业务出错。
核心特征:其他事务更新修改已有数据,导致当前事务数据不稳定。
3、幻读:查询范围数据出现“幻觉”
通俗解释:同一个事务中,做范围统计、批量查询时,其他事务新增或删除数据,导致当前事务前后查询的数据行数不一致,就像出现了幻觉。
业务场景举例:事务A统计全部商品,只有1条数据。事务B新增商品并提交,事务A再次统计,突然多出一条数据。
核心特征:其他事务新增/删除数据,影响当前事务范围查询结果。
(插图建议:三大读异常场景对比图解,直观区分三者差异)
三、MySQL四大事务隔离级别(从低到高实战拆解)
标准SQL规范定义了四种事务隔离级别,InnoDB引擎全部兼容。按照隔离度从低到高排序依次是:
读未提交(Read Uncommitted)
读已提交(Read Committed)
可重复读(Repeatable Read,MySQL默认级别)
串行化(Serializable)
我结合实操案例逐一讲解,大家可以清晰看到每一种级别能解决什么问题,又会遗留哪些隐患。
1、读未提交:Read Uncommitted(最低级别,生产禁用)
核心特点:所有事务可以直接读取其他事务未提交的数据,没有任何读取限制。
问题情况:脏读、不可重复读、幻读三类问题全部存在
我们简单实操验证:
事务A、事务B两个会话,统一执行以下语句切换隔离级别并开启事务:
-- 设置当前会话隔离级别SETSESSIONTRANSACTIONISOLATIONLEVELREADUNCOMMITTED;-- 开启事务STARTTRANSACTION;1. 事务A查询库存,结果为100;
2. 事务B更新库存为50,不执行提交操作;
3. 事务A再次查询,直接读取到未提交的库存50,脏读现象出现;
4. 事务B执行回滚,数据库库存恢复100。
此时事务A拿到的50就是完全无效的脏数据,极易引发业务bug。
适用场景:生产环境基本不会使用,仅用于本地测试学习。并发性能极高,但数据安全性极差。
2、读已提交:Read Committed(RC)
核心特点:事务只能读取其他事务已经提交落地的数据,读取不到未提交的临时数据。
解决问题:彻底杜绝脏读
遗留问题:依旧存在不可重复读、幻读
执行以下语句切换隔离级别:
SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;实操下来可以发现:事务B修改数据但不提交时,事务A的数据不会变化,成功规避脏读。但如果事务B修改数据并提交,事务A在同一个事务内两次查询结果不一致,也就是典型的不可重复读问题。
适用场景:后台数据统计、资讯展示、非核心页面查询。这类业务允许短暂的数据不一致,优先保证接口并发速度。
3、可重复读:Repeatable Read(RR,MySQL默认)
核心特点:同一个事务内,第一次查询会生成数据快照,整个事务生命周期内,都会读取快照数据。外部事务修改、提交数据,都不会影响当前事务的查询结果。
解决问题:脏读、不可重复读全部解决
遗留问题:依然存在幻读
这也是MySQL默认采用该级别的核心原因,完美平衡了性能和数据安全性,足以覆盖绝大多数线上业务场景。
简单来说,事务A开启事务后,无论外部事务怎么修改提交数据,自身查询结果始终保持不变,保证了单事务数据稳定。
这里很多人都有个疑问:RR 既然叫可重复读,为什么还会出现幻读?这也是很多面试题的陷阱。下面我给大家一套百分百可复现的实战案例,彻底讲透这个问题。
实战前提:两个事务均使用 MySQL 默认隔离级别 可重复读(RR)
两个会话统一执行以下语句:
-- 会话A、会话B 统一执行SETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;STARTTRANSACTION;完整复现步骤:
1、事务A开启事务,查询全部商品数据(当前库内仅1条数据)
-- 事务A执行SELECT*FROMgoods_stock;查询结果:仅1条【夏季短袖,库存100】,事务A生成数据快照,固化当前数据状态。
2、事务B开启事务,新增商品数据并主动提交
-- 事务B执行INSERTINTOgoods_stock(goods_name,stock)VALUES('冬季卫衣',200);COMMIT;3、事务A不结束当前事务,再次执行全表查询
-- 事务A再次执行SELECT*FROMgoods_stock;此时可以看到:事务A依旧只能查询到1条旧数据,看不到事务B新增的数据。
很多人看到这里都会疑惑:不是说 RR 存在幻读吗?为什么复现不出来?
这就是网上大部分文章的误区:MySQL 的 RR 隔离级别,依靠快照机制规避了快照读(普通SELECT)的幻读,但是完全挡不住当前读的幻读问题。
我们继续操作,真正复现幻读:
4、事务A持续不提交,执行更新操作(当前读)
-- 事务A执行更新UPDATEgoods_stockSETstock=0;5、事务A再次执行查询语句
SELECT*FROMgoods_stock;此时诡异的业务问题直接复现:事务A查询到了2条数据,不仅有原本的夏季短袖,还多出了刚刚新增的冬季卫衣,且两条数据的库存都被更新为0。
这就是标准的幻读:同一个事务内,原本判定库中只有一条商品、准备批量清空库存,结果执行更新后突然多出一条数据,导致业务逻辑和预期完全不符,这也是库存超卖、批量更新遗漏数据的核心元凶。
幻读核心总结:
快照读(普通 SELECT 查询):RR 级别可以规避幻读
当前读(UPDATE/INSERT/DELETE/SELECT ... FOR UPDATE):RR 级别无法规避幻读
这也是为什么订单、库存等高并发核心业务,偶尔会出现莫名其妙的数据异常。
适用场景:绝大多数线上核心业务,订单、支付、库存、用户数据读写场景基本都用此级别。
4、串行化:Serializable(最高隔离级别)
核心特点:数据库最高的隔离级别,直接强制所有事务串行执行。读写操作互相阻塞,同一时间仅允许单个事务操作数据表。
解决问题:脏读、不可重复读、幻读三类异常全部彻底解决
缺点:并发性能极差,极易出现事务阻塞、请求超时,系统吞吐量大幅下降
适用场景:仅用于数据一致性要求极高、并发量低的场景,比如金融资金清算、财务对账、核心结算业务。
四、四大隔离级别全方位汇总对比
我整理了一份汇总对照表,清晰罗列每种隔离级别的优缺点与适配场景,方便大家快速对比查阅:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 并发性能 | 适用场景 |
|---|---|---|---|---|---|
| 读未提交 | 存在 | 存在 | 存在 | 最高 | 几乎不用 |
| 读已提交 | 不存在 | 存在 | 存在 | 较高 | 普通展示、统计业务 |
| 可重复读(默认) | 不存在 | 不存在 | 存在 | 中等 | 绝大多数线上核心业务 |
| 串行化 | 不存在 | 不存在 | 不存在 | 最低 | 金融对账、资金结算 |
五、线上业务如何选型?直接抄作业
很多开发者知识点背得很熟,但落地到项目就不知道该怎么选。这里我结合多年线上运维经验,给大家一套可以直接落地的选型标准:
绝大多数项目:直接沿用 MySQL 默认的可重复读 RR,兼顾性能和数据安全,综合性价比最高,适配订单、用户、库存等核心业务。
后台报表、数据统计、内容展示:选用读已提交 RC,轻微的数据不一致不会影响业务,还能有效提升接口并发速度。
资金、对账、清算、核心交易:极致一致性场景,可酌情使用串行化,优先保障数据绝对准确。
生产环境绝对禁止读未提交,数据风险极高,完全没有使用价值。
六、最后总结
在我看来,事务隔离级别不需要死记硬背,核心逻辑特别简单:
隔离越低,并发越强、数据越容易出问题;隔离越高,数据越稳、接口性能越差。
脏读、不可重复读、幻读从来不是数据库bug,而是并发读写场景下的正常现象。我们学习隔离级别,本质是根据自身业务的一致性要求,权衡性能与数据安全,规避对应的业务漏洞,解决库存超卖、对账异常、数据错乱等线上疑难问题。
吃透这篇文章,以后再遇到数据库随机数据异常,不用再盲目重启服务、束手无策,可以精准定位问题根源。
