不可重复读 vs 幻读

在数据库事务一致性中,不可重复读(Non-repeatable Read)和幻读(Phantom Read)是两个容易混淆但本质不同的问题。为什么他们都是“同一个事务中两次读的结果不一样”,还非要分成两种呢,因为发生的根源、影响范围和需要的应对策略完全不同。
另外,一个常见问题:“如果数据被删除,再次读不到,算不可重复读还是幻读?”


不可重复读(Non-repeatable Read)

1. 定义

在一个事务中,前后两次读取同一行记录,但第二次读取的值和第一次不同,原因是另一个事务在中间修改或删除了该行数据并提交了

2. 举例

假设有一张 account 表:

idnamebalance
1张三1000

两个事务并发执行:

事务 A:
BEGIN;
SELECT balance FROM account WHERE id = 1;  -- 得到 1000
事务 B:
BEGIN;
UPDATE account SET balance = 800 WHERE id = 1;
COMMIT;
事务 A 继续:
SELECT balance FROM account WHERE id = 1;  -- 得到 800,和第一次不一致

这种前后两次读取同一行结果不同的现象,就是不可重复读

3. 删除是否也属于不可重复读?

是的。若事务 B 在中间 删除了该行,事务 A 第二次读的时候发现记录不存在,也属于不可重复读的一种形式。

-- 事务 B 删除该行:
DELETE FROM account WHERE id = 1;
COMMIT;

-- 事务 A 再次读:
SELECT balance FROM account WHERE id = 1;  -- 无结果(行被删除)

虽然不是字段值变化,而是整行消失,本质上仍然是“你试图读同一行,结果不同”,因此归类为不可重复读


二、幻读

1. 定义

在一个事务中,前后两次对某个范围(WHERE 条件)进行查询时,第二次读出的行数比第一次多或少了,原因是另一个事务插入或删除了满足条件的新记录

2. 举例

假设有一张 orders 表:

iduser_idamount
11100
21200
事务 A:
BEGIN;
SELECT COUNT(*) FROM orders WHERE user_id = 1;  -- 返回 2 条
事务 B:
INSERT INTO orders (id, user_id, amount) VALUES (3, 1, 300);
COMMIT;
事务 A 继续:
SELECT COUNT(*) FROM orders WHERE user_id = 1;  -- 返回 3 条,多了一条

这就是幻读:查询范围相同,但结果多出了“幻影”一样的新记录。


不可重复读 vs 幻读:对比总结

项目不可重复读幻读
关注点同一条记录查询范围内的数据行
原因其他事务修改或删除了已存在的记录其他事务插入或删除了新记录
表现同一行数据两次读取不一致或消失相同条件下读取结果行数变多或变少
解决方式行锁(Record Lock)间隙锁(Gap Lock)或 Serializable 隔离级别

为什么非要区分这两者?

虽然现象相似,但两者必须区分,原因如下:

  1. 业务影响不同

    • 不可重复读更多影响的是一致性判断和逻辑校验
    • 幻读会影响聚合、统计、分页、唯一性校验等范围逻辑
  2. 隔离级别影响不同

    • REPEATABLE READ 可以防止不可重复读,但可能仍会发生幻读(除非使用间隙锁)。
    • 只有 SERIALIZABLE 能完全防止幻读。
  3. 底层机制不同

    • 不可重复读可通过加锁特定记录防止(行锁)。
    • 幻读需要锁定“空隙”或范围(间隙锁、next-key lock),这是 InnoDB 引擎特有的处理。

一个生活类比

你在图书馆查阅:

  • 不可重复读:你两次查阅同一本书,第一次看到第1版,第二次被人换成了第2版,或整本书消失了。
  • 幻读:你两次查阅“数据库类书籍”区域,第一次有5本,第二次突然多了1本新书。

两个现象不同,因此数据库必须分别以不同方式应对,以免 应对不足|或者|应对过多资源浪费。


总结

不可重复读强调的是“同一条记录变了”,包括更新和删除;
幻读强调的是“范围内的数据增删”,出现了新的行 / 少了某些行。

MySQL中的不可重复是两种并发控制问题。 不可重复(Non-repeatable Read)是指在一个事务中,多次取同一数据,在这个事务还没有结束时,另外一个事务也修改了这个数据,导致多次取的结果不一致。换句话说,一个事务在执行期间,另一个事务对数据进行了修改,导致当前事务多次取的结果不同。 (Phantom Read)是指在一个事务中,多次查询同一范围的数据,在这个事务还没有结束时,另外一个事务插入了符合同一范围的数据,导致多次查询的结果不一致。换句话说,一个事务在执行期间,另一个事务对数据进行了插入或删除操作,导致当前事务多次查询的结果不同。 为了解决不可重复的问题,MySQL提供了不同的隔离级别。隔离级别定义了一个事务对其他事务的可见性和影响范围。MySQL支持四个隔离级别: 1. 未提交(Read Uncommitted):最低级别,事务可以取到其他事务未提交的数据,可能出现脏不可重复问题。 2. 已提交(Read Committed):默认级别,事务只能取到其他事务已提交的数据,可以避免脏,但仍可能出现不可重复问题。 3. 可重复(Repeatable Read):事务在执行期间,多次取同一数据时会返回一致的结果,可以避免不可重复,但仍可能出现问题。 4. 串行化(Serializable):最高级别,事务依次执行,避免了脏不可重复问题,但牺牲了并发性能。 开发者可以根据具体需求选择合适的隔离级别来解决不可重复问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值