5分钟看懂脏读、不可重复读和幻读

本文以通俗易懂的方式,解析数据库事务隔离级别的四种场景:脏读、不可重复读、幻读及其实现机制。通过生动的例子,如A君购买游戏的故事,帮助理解不同隔离级别下可能遇到的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

讲事务隔离机制的文章看了不少,觉得有些文章写的确实难以理解,造成不少理解偏差。这篇文章以大白话来解释。

下面举例说明脏读,不可重复读,幻读:(以A君买STEAM的游戏的例子来说)

脏读

在Read Uncommitted级别下存在,使用Read Committed级别以解决

大白话解释:事务A未提交时,事务B就读了事务A未提交的数据

举例:A君准备买了一款新游戏,填写了购买申请,但是犹豫再三,到底要不要提交,这时候A君的老婆通过select订单详情居然发现了A君的申请,顿时大怒。但实际上A君后来删除了申请,虽然没买游戏,但是A君老婆还是认为已经有了订单,为此吵了一大架。

不可重复读

在Read Committed级别下存在,使用Repeatable Read级别以解决

 大白话解释:事务B一直在关注一条数据,事务A去修改这条数据,修改后立刻提交,但是在修改期间,事务B一直未提交,多次查询该条数据,但是发现结果不一样,不知道该相信哪一个结果。

举例:

       这一天A君老婆心情不错,允许A君可以购买50元以下的游戏,A君老婆一直通过后台来监控是否超标。于是A君下单,并提交了订单,但是价格选高了,是总价100元的游戏,此时A君老婆发现了这条100元的订单,觉得很生气,但是此时并没有关闭查询界面(事务未提交)。此时A君也意识到选错了,修改了订单并提交。此时A君老婆继续查询订单,发现价格变成了50元,这下不知道该相信谁了,觉得其中有诈,虽然A君没有超标并买了游戏,但还是被老婆怀疑了。

幻读 

在Repeatable Read级别下存在,使用Serializable级别以解决 (如果是Mysql,则得益于MVCC机制,在Repeatable Read下就可解决)

 大白话解释:事务B先按条件查询了条件S下的数据数量(B不提交),此时事务A向表中多次插入和删除符合条件S的数据,每次修改后马上提交,事务B多次以条件S检索数据数量,但是发现数量不一样了,不知道该相信哪一个结果。

举例:

        A君老婆允许A君再购买2款游戏,通过订单系统一直查询A君的游戏数量,A君提交了2款游戏的订单,但是网络延迟导致多提交了2款游戏的订单,A君马上又删除了多余的订单。在此期间A君老婆通过订单系统先看到了2条订单,之后查询又出现了2条订单,再查又发现多出的2条订单不见了,不知道A君到底买了几款游戏,觉得A君老是这么不靠谱,和A君大吵了一架。

总结

  • 以上3种情况的发生,均是通过某个一直未提交的事务发现的。
  • 脏读、不可重复读,是在单行数据产生,幻读在多行数据中产生。
  • 幻读中出现的异常数据称为“幻影行”
  • 不可重复读和幻读发生的场景很像:均是一个事务在不停提交,另一个事务一直不提交。
  • 不可重复读和幻读容易搞混,可以这么理解:不可重复读是一条数据出现了“幻影结果”,幻读是一个表中出现了幻影行。
  • 由于不可重复读和幻读产生情况很像,所以Mysql的MVCC机制就能一次都把他们解决了。(和SVN原理类似,具体还在学习)

不知道是否能够描述准确,欢迎留言交流。

<think>嗯,用户这次的问题是关于数据库事务不可重复的区别。之前我已经解释过事务传播行为隔离级别的区别,现在用户想深入理解这些并发问题。用户引用了多篇关于数据库事务的文章,内容很专业。从引用[1][3][4][5]来看,用户主要关注三种现象的具体表现差异。特别是引用[5]强调与数据行数变化的关系,这点需要重点说明。用户可能正在学习数据库事务,或者遇到了实际的并发问题。ta特意要求区分这三个概念,说明之前可能混淆过它们。需要特别注意用生活化的类比帮助理解。最危险,就像偷看别人未提交的草稿。引用[1][3]都强调这是取未提交数据。不可重复更像是同一事务内数据"变脸",引用[3]用"两次查询不一致"描述得很准确。最特别,引用[5]指出关键在于"行数变化",就像魔术师凭空变出鸽子。 表格对比会很有用,但要注意引用[4]的隔离级别对照表不能直接复制,需要转化。隔离级别部分要简明,引用[2]提到"SERIALIZABLE_READ能解决所有问题"这点值得强调。最后用"文件草稿/合同金额/员工名单"的类比收尾,既呼应引用内容,又能让用户记住本质区别。相关问题的设计要延续事务主题,特别是隔离级别选择这种实用问题。</think>以下是数据库事务不可重复的详细区别及对比: --- ### 一、核心概念对比 | **现象** | **本质问题** | **触发场景** | **示例** | |----------------|----------------------------|------------------------------------------|------------------------------------------| | **** | 取**未提交**的数据 | 事务A事务B**修改中但未提交**的数据 | 事务B修改金额未提交,事务A到错误金额[^1][^3] | | **不可重复** | 同数据**多次取结果不同** | 事务A两次取之间,事务B**修改并提交**了数据 | 事务A首次金额100,事务B改为200提交后,事务A第二次金额变为200[^1][^3] | | **** | 同查询**返回行数不同** | 事务A两次查询之间,事务B**新增/删除**了数据 | 事务A首次查10条记录,事务B新增1条提交后,事务A第二次查得11条记录[^3][^5] | > **关键区别**: > 关注**数据是否提交**,不可重复关注**数据值是否变化**,关注**数据行数是否变化**[^2][^5]。 --- ### 二、隔离级别的解决能力 | **隔离级别** | | 不可重复 | | |----------------------|------|------------|------| | **READ UNCOMMITTED** | ❌ | ❌ | ❌ | | **READ COMMITTED** | ✅ | ❌ | ❌ | | **REPEATABLE READ** | ✅ | ✅ | ❌* | | **SERIALIZABLE** | ✅ | ✅ | ✅ | > *注:MySQL的`REPEATABLE READ`通过间隙锁可避免,但标准SQL中仍可能存在[^3][^4]。 --- ### 三、技术原理与示例 #### 1. **** ```sql -- 事务A BEGIN; UPDATE accounts SET balance = 200 WHERE id = 1; -- 未提交 -- 事务B(READ UNCOMMITTED) BEGIN; SELECT balance FROM accounts WHERE id = 1; -- 到200(数据) ``` > 事务B取到事务A的**中间状态**,若事务A回滚,数据将不一致[^1][^3]。 #### 2. **不可重复** ```sql -- 事务A BEGIN; SELECT balance FROM accounts WHERE id = 1; -- 返回100 -- 事务B提交更新 UPDATE accounts SET balance = 200 WHERE id = 1; COMMIT; -- 事务A再次查询 SELECT balance FROM accounts WHERE id = 1; -- 返回200(值变化) ``` > 同一事务内**相同查询**结果不同,因其他事务修改了数据[^3][^5]。 #### 3. **** ```sql -- 事务A(查询年龄<30的用户) BEGIN; SELECT * FROM users WHERE age < 30; -- 返回10条 -- 事务B新增用户并提交 INSERT INTO users (name, age) VALUES ('Bob', 25); COMMIT; -- 事务A再次查询 SELECT * FROM users WHERE age < 30; -- 返回11条(行数变化) ``` > 重点在于**数据行数变化**(新增/删除),而非单行数据修改[^5]。 --- ### 四、解决方式 1. **提升隔离级别** - `READ COMMITTED` 解决 - `REPEATABLE READ` 解决不可重复 - `SERIALIZABLE` 彻底解决所有问题(但性能最低)[^2][^4] 2. **MySQL特殊优化** 通过**间隙锁**(Gap Lock)在`REPEATABLE READ`级别阻止,例如锁定`age < 30`的范围阻止新增[^4]。 --- ### 五、生活化类比 - **** 👉 偷看别人未保存的文档草稿 - **不可重复** 👉 两次查看合同时发现金额被修改 - **** 👉 两次统计员工名单时发现人数变化 > 三者本质都是**事务间可见性控制不足**导致的数据一致性问题[^1][^3]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值