脏读、不可重复读、幻读?一文扫盲数据库三大“读“问题

想象一下:你在银行查看账户余额时,数字在你眼前变来变去;或者明明没有记录的操作,却突然冒出新数据。这不是系统故障,而是数据库事务隔离的三大经典问题!今天我们就来揭开这些神秘现象的面纱。

一、事务隔离的"三座大山"️

在数据库世界中,多个事务同时操作数据时会产生三种典型问题:

问题类型 出现场景 危害程度 类比场景
脏读 读取未提交的数据 ⚠️⚠️⚠️高危 看到别人未提交的草稿
不可重复读 同一事务内读取结果不一致 ⚠️⚠️中危 账户余额在你眼前变化
幻读 同一查询突然出现新记录 ⚠️低危 凭空多出一笔交易记录

二、脏读(Dirty Read):看到别人的"草稿"

什么是脏读?

脏读是指一个事务读取了另一个事务尚未提交的数据修改。就像你看到同事正在写的方案草稿,但最终方案可能完全不同!

生活化示例

假设小明和小红共同编辑一份报告:

  1. 小明开始修改报告(但未保存)
  2. 小红打开了报告,看到了小明的修改
  3. 小明发现修改有误,撤销了所有更改
  4. 小红看到的"修改"其实从未真正存在过!

代码演示

-- 会话1(事务A)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE accounts SET balance = balance - 500 WHERE user = '小明'; -- 未提交

-- 会话2(事务B)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM accounts WHERE user = '小明'; 
-- 看到扣除500后的余额(脏数据!)

-- 会话1回滚
ROLLBACK;

-- 会话2再次查询
SELECT balance FROM accounts WHERE user = '小明'; 
-- 余额恢复原样,之前读到的数据无效!

危害性

  • 可能导致业务决策基于不存在的数据
  • 金融系统中可能造成灾难性后果(如基于错误余额放贷)

三、不可重复读(Non-Repeatable Read):数据在你眼前"变脸"

什么是不可重复读?

不可重复读指在同一个事务内,多次读取同一数据集合得到不同结果。就像你查看账户余额时,数字在你眼前不断变化!

生活化示例

  1. 早上9点查看股票价格:100元/股
  2. 刷新页面后看到:95元/股
  3. 再刷新又变成:98元/股
  4. 同一时刻的价格在你多次查看时不断变化

代码演示

-- 会话1(事务A)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE user = '小明'; -- 返回1000元

-- 会话2(事务B)更新并提交
UPDATE accounts SET balance = 500 WHERE user = '小明';
COMMIT;

-- 会话1再次查询
SELECT balance FROM accounts WHERE user = '小明'; 
-- 返回500元!同一事务内结果不一致

图解原理

事务A 数据库 事务B 查询余额(1000元) 更新余额为500 更新成功 提交 再次查询余额 返回500元 事务A 数据库 事务B

危害性

  • 导致事务内数据不一致
  • 影响统计、报表类操作的准确性

四、幻读(Phantom Read):数据凭空"变魔术"

什么是幻读?

幻读指在同一事务中,相同的查询条件第二次查询时突然多出记录。就像你数书架上的书时,每次数出的数量都不一样!

生活化示例

  1. 图书馆管理员统计计算机类书籍:100本
  2. 此时有新书上架(5本计算机书)
  3. 再次统计:105本
  4. 管理员困惑:多出来的书是哪来的?

代码演示

-- 会话1(事务A)
START TRANSACTION;
SELECT COUNT(*) FROM books WHERE type = '计算机'; -- 返回100

-- 会话2(事务B)插入新书并提交
INSERT INTO books(name, type) VALUES('MySQL实战','计算机');
COMMIT;

-- 会话1再次查询
SELECT COUNT(*) FROM books WHERE type = '计算机'; 
-- 返回101!出现"幻影行"

图解原理

事务A开始
查询计算机书籍=100本
事务B插入新计算机书
提交
事务A再次查询
结果=101本

幻读 vs 不可重复读

特性 不可重复读 幻读
操作对象 已存在的单条数据 满足条件的记录集合
现象 数据值改变 记录数量改变
关注点 数据更新(UPDATE) 数据新增(INSERT)
解决方式 行级锁 范围锁

五、如何解决这些问题?

事务隔离级别是答案

MySQL通过四种隔离级别解决不同问题:

隔离级别 脏读 不可重复读 幻读 实现机制
读未提交 无保护
读已提交 快照读
可重复读(默认) MVCC多版本控制
串行化 完全锁表

✅ = 解决,❌ = 未解决

代码设置隔离级别

-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置为读已提交(解决脏读)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置为可重复读(解决脏读+不可重复读)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 设置为串行化(解决所有问题)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

不同场景推荐

脏读、不可重复读、幻读?一文扫盲数据库三大“读“问题_第1张图片

六、实战解决方案

解决脏读

使用读已提交(READ COMMITTED) 隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

解决不可重复读

使用可重复读(REPEATABLE READ) 隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

解决幻读

方案1:升级到串行化(SERIALIZABLE) 隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

方案2:在可重复读级别使用间隙锁

SELECT * FROM books WHERE type = '计算机' FOR UPDATE;

七、总结与最佳实践

  1. 脏读是读取未提交数据 - 使用读已提交级别解决
  2. 不可重复读是数据值变化 - 使用可重复读级别解决
  3. 幻读是记录数变化 - 需要串行化锁机制解决

黄金法则:选择满足业务需求的最低隔离级别,因为隔离级别越高,性能代价越大!

业务场景 推荐隔离级别 理由
金融交易 可重复读或串行化 保证绝对一致性
内容管理系统 读已提交 平衡性能与一致性
数据仓库分析 读未提交 只读场景可接受脏数据
票务系统 串行化 防止超卖

最后的小测试:
在电商系统中:

  • 用户下单时库存检查应避免______?
  • 生成销售报表时应避免______?
  • 管理员查看未付款订单时应避免______?

欢迎在评论区留下你的答案!掌握这三种"读"问题,你将能设计出更健壮的数据库系统!

(本文适用于MySQL 8.0+版本,不同数据库实现可能有差异)

你可能感兴趣的:(脏读、不可重复读、幻读?一文扫盲数据库三大“读“问题)