MySQL可重复读级别解决幻读问题

MySQL 中,可重复读(REPEATABLE READ)级别的幻读(Phantom Read)问题可以通过以下方法解决:


什么是幻读?

幻读是指 同一事务中执行相同的 SELECT 语句,但因其他事务的 INSERTDELETE 使得数据发生变化,导致查询结果前后不一致

场景
  • 事务A 开始,并执行 SELECT 语句查询满足条件的记录。
  • 事务B 在事务A未提交之前,插入或删除满足条件的新数据。
  • 事务A 再次执行相同的查询,但查询结果发生变化,这就是 幻读
示例
-- 事务 A 开始
START TRANSACTION;
SELECT * FROM emp WHERE deptno = 10; -- 查询出 3 条数据

-- 事务 B 插入新数据
INSERT INTO emp (empno, ename, deptno) VALUES (999, 'NewEmp', 10);
COMMIT;  -- 事务 B 提交

-- 事务 A 再次查询
SELECT * FROM emp WHERE deptno = 10; -- 现在查询出 4 条数据(幻读)

REPEATABLE READ 级别下,普通 SELECT 不能防止 幻读,但可以使用 行级锁或表级锁 解决。


解决方案

方式一:使用 SERIALIZABLE 事务隔离级别

  • SERIALIZABLE 是 MySQL 最高级别的事务隔离,可以防止 幻读,但会导致并发性能降低。
  • 事务中的 SELECT 语句会使用表级锁锁住整个表),避免其他事务插入或删除数据。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM emp WHERE deptno = 10;

缺点

  • 由于 所有读取都变成串行化执行,会大幅降低并发性能。
  • 适用于强一致性要求的系统,如银行交易系统。

方式二:使用 SELECT ... LOCK IN SHARE MODE**

  • LOCK IN SHARE MODE 使得查询的行被共享锁锁定,防止其他事务修改或删除这些行,但 不能防止 INSERT 造成的幻读
START TRANSACTION;
SELECT * FROM emp WHERE deptno = 10 LOCK IN SHARE MODE;

适用场景

  • 适用于 只希望防止数据修改(UPDATE/DELETE),但允许插入新数据的情况。

不能防止 INSERT

  • 其他事务仍然可以 INSERT 新数据,导致 幻读问题仍然存在

方式三:使用 SELECT ... FOR UPDATE**

  • FOR UPDATE查询的行加上排他锁(X 锁),防止其他事务 INSERTUPDATEDELETE,从而解决幻读
START TRANSACTION;
SELECT * FROM emp WHERE deptno = 10 FOR UPDATE;

适用于

  • 需要防止幻读,同时允许高并发的场景。
  • 适用于 并发事务修改同一数据集 的情况,如 订单处理库存管理

限制

  • 只锁定 已存在的记录,无法阻止 INSERT 新记录 造成的幻读。
  • 可能会出现间隙锁(Gap Lock),如果 innodb_locks_unsafe_for_binlog 启用,可能导致锁失效。

方式四:使用 InnoDB 的间隙锁(Gap Lock)

  • MySQL InnoDB 默认在 REPEATABLE READ 级别使用间隙锁(Gap Lock),它不仅锁定现有记录,还会锁住索引范围内的空隙,防止新数据插入,解决幻读问题
START TRANSACTION;
SELECT * FROM emp WHERE deptno = 10 FOR UPDATE;
  • MySQL 默认 REPEATABLE READ 级别 + FOR UPDATE 会自动启用间隙锁(Gap Lock),防止 INSERT

  • 但如果 innodb_locks_unsafe_for_binlog = ON,MySQL 可能不会加 Gap Lock,导致幻读问题依然存在!

适用于

  • 事务需要保证数据查询前后一致性的业务场景,如 库存管理、银行账户交易

解决方案对比

方案能防止幻读影响并发性能适用场景
SERIALIZABLE 事务级别能防止🔴 低(表级锁)银行系统、强一致性要求场景
LOCK IN SHARE MODE不能防止仅防止 UPDATE/DELETE,不防 INSERT
FOR UPDATE能防止🟡 中等(行级锁)订单管理、库存管理
Gap Lock(InnoDB)能防止🟡 中等(索引范围锁)需要防止 INSERT 幻读的事务

结论

  • 如果允许并发,但想防止幻读SELECT ... FOR UPDATE(推荐)
  • 如果完全禁止 INSERT 导致的幻读InnoDB 默认 REPEATABLE READ + Gap Lock 即可防止。
  • 如果你需要最高的事务隔离使用 SERIALIZABLE,但会影响并发性能。

在 MySQL 默认的 REPEATABLE READ 级别 下,使用 SELECT ... FOR UPDATE 即可防止幻读,而 不必强制升级到 SERIALIZABLE

最佳实践

START TRANSACTION;
SELECT * FROM emp WHERE deptno = 10 FOR UPDATE;
COMMIT;
MySQL可重复级别解决了部分问题,但并没有完全解决是指在一个事务中,当某个范围内的记录被其他事务插入或者删除时,当前事务再次取该范围的记录时,会出现不一致的现象。 在MySQL可重复级别下,使用了行级锁来保证事务的隔离性,防止并发事务之间相互干扰。具体而言,可重复级别使用了next-key锁,将索引的范围加上了锁,防止其他事务对该范围内的记录进行插入或者删除操作。这样可以确保在一个事务中多次取同一范围的记录时,结果始终相同,避免了取到其他事务插入或删除的影行。 然而,可重复级别并不能完全解决问题。因为它只能保证在同一个事务内,对同一范围的查询结果是一致的,但无法防止其他事务在当前事务查询过程中插入新的记录或者删除现有的记录,从而导致。也就是说,对于已经存在的记录,可重复级别可以保证取的结果是一致的,但对于新插入的记录,则无法预测是否会被查询到。 为了解决问题MySQL引入了更高的隔离级别,如串行化。在串行化级别下,对于同一范围的查询结果,在事务开始和提交之间都会加上行级锁,防止其他事务对该范围内的记录进行插入或者删除操作,从而完全解决问题。但需要注意的是,在串行化级别下,可能会导致并发性能下降,因为所有的事务都需要按照先后顺序依次执行,无法并发进行。 综上所述,MySQL可重复级别可以解决部分问题,但无法完全解决。为了彻底解决问题,需要使用更高的隔离级别,如串行化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值