SQL 幻读是如何产生的?

来源:站长论坛作者:下班再修头衔:程序员
导读:本期聚焦于小伙伴创作的《SQL 幻读是如何产生的?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《SQL 幻读是如何产生的?》有用,将其分享出去将是对创作者最好的鼓励。

幻读指的是在同一个事务中,前后两次执行相同的范围查询,后一次查询看到了前一次查询没有看到的行数据,这些新增的行数据通常是其他事务在两次查询之间插入并提交的数据。幻读和不可重复读容易混淆,不可重复读是同一行数据的内容被修改,幻读是出现了之前不存在的新行。

SQL 幻读是如何产生的?

幻读产生的典型场景

我们可以通过一个具体的MySQL InnoDB引擎下的示例来还原幻读的产生过程,首先准备一张测试表,表结构如下:

-- 创建测试表
CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    age INT NOT NULL,
    INDEX idx_age (age)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入初始数据
INSERT INTO user (name, age) VALUES ('张三', 20), ('李四', 22), ('王五', 25);

假设当前数据库的事务隔离级别是可重复读(Repeatable Read),这是MySQL InnoDB的默认隔离级别,我们开启两个事务来模拟并发场景:

事务执行步骤

步骤事务A事务B
1START TRANSACTION;START TRANSACTION;
2SELECT * FROM user WHERE age > 20;
-- 返回李四(22)、王五(25)两条数据
3INSERT INTO user (name, age) VALUES ('赵六', 23);
COMMIT;
4SELECT * FROM user WHERE age > 20;
-- 此时还是返回李四、王五两条数据,可重复读级别下普通查询看不到新插入的行
5UPDATE user SET age = 24 WHERE age > 20;
-- 此时会更新3行数据,包括事务B插入的赵六
6SELECT * FROM user WHERE age > 20;
-- 此时返回李四(24)、王五(25)、赵六(24)三条数据,出现了之前查询没有的行,产生幻读

幻读产生的底层原因

幻读的产生和数据库的多版本并发控制(MVCC)以及锁机制有关,在可重复读隔离级别下:

  • 普通的SELECT查询属于快照读,事务启动时生成一致性视图,整个事务期间读取的都是这个视图的数据,所以不会看到其他事务新插入并提交的数据,这也是步骤4中事务A第二次查询没有看到赵六的原因。
  • UPDATEDELETEINSERT以及加锁的SELECT(比如SELECT ... FOR UPDATE)属于当前读,读取的是最新的数据版本,并且会对读取到的行加锁。步骤5中事务A执行UPDATE是当前读,会读取到事务B已经提交的赵六这条数据,并且更新它。
  • UPDATE操作修改了之前查询范围外的数据后,再次执行快照读时,由于MVCC的可见性判断规则,已经被当前读修改过的数据会被纳入当前事务的可见范围,因此步骤6的查询就会看到赵六这条新行,幻读就产生了。

不同隔离级别对幻读的处理

SQL标准定义了四种事务隔离级别,不同级别对幻读的支持情况不同:

  • 读未提交(Read Uncommitted):会出现脏读、不可重复读、幻读,因为可以读取到其他事务未提交的插入数据。
  • 读已提交(Read Committed):不会出现脏读,但会出现不可重复读和幻读,每次查询都会生成新的一致性视图,所以能看到其他事务已提交的插入数据。
  • 可重复读(Repeatable Read):MySQL的InnoDB引擎通过间隙锁(Gap Lock)机制,在加锁查询时可以完全避免幻读,但普通快照读场景下还是可能出现上述示例中的幻读情况。
  • 串行化(Serializable):完全避免幻读,所有事务串行执行,读操作也会加锁,其他事务无法插入符合查询范围的数据。

如何避免幻读

如果业务场景中需要完全避免幻读,可以采取以下方案:

  • 将事务隔离级别设置为串行化,但会大幅降低并发性能,适合并发量低的场景。
  • 在可重复读隔离级别下,对范围查询使用SELECT ... FOR UPDATE加锁,触发间隙锁,阻止其他事务在查询范围内插入数据,示例代码如下:
-- 加锁查询,防止其他事务插入age>20的数据
START TRANSACTION;
SELECT * FROM user WHERE age > 20 FOR UPDATE;
-- 此时其他事务无法插入age>20的新行,直到当前事务提交
-- 后续的业务操作
COMMIT;

需要注意的是,间隙锁只在可重复读及以上隔离级别生效,读已提交隔离级别下没有间隙锁,无法通过这种方式避免幻读。

SQL幻读事务隔离级别InnoDBMySQL修改时间:2026-07-07 02:54:23

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。