导读:本期聚焦于不吃香菜创作的《MySQL的事务隔离级别怎么理解?用案例带你一次讲清楚》,敬请观看详情。两个事务同时修改同一行数据,一个事务还没提交,另一个事务读到了这个未提交的修改,随后前者回滚了。这个被读到的数据算有效吗?这类问题正是 MySQL 事务隔离级别要解决的并发控制核心。MySQL InnoDB 支持读未提交、读已提交、可重复读、串行化四种隔离级别,默认是可重复读。不同级别对应不同的锁策略和 MVCC 多版本并发控制行为,能分别避免脏读、不可重复读、幻读等现象,但也会带来不同的并发性能和锁冲突。本文通过具体会话案例演示这四种隔离级别在相同操作下的不同结果,说明隔离级别如何设置、如何查看,以及工程上怎样权衡一致性和性能。

事务隔离级别不是单纯的概念题,它直接决定了并发事务在读写同一批数据时能看到什么、会不会读到中间状态。MySQL 的 InnoDB 存储引擎实现了四种隔离级别,不同的级别在一致性、锁冲突和吞吐量之间做出了不同取舍。要理解它,最好的方式不是背诵定义,而是把两个会话放到同样的表上,观察同一条 SQL 在不同隔离级别下的返回结果。

MySQL的事务隔离级别怎么理解?用案例带你一次讲清楚

一、并发事务带来的三个经典问题

事务需要满足 ACID 特性,其中隔离性专门处理多个事务同时执行时的相互影响。如果完全不加以控制,就可能出现脏读、不可重复读和幻读。脏读是指一个事务读到了另一个事务尚未提交的修改,一旦对方回滚,读到的数据就是无效的。不可重复读是指在同一个事务内,两次读取同一行数据得到不同结果,原因是其他事务在两次读取之间提交了修改。幻读则与范围查询有关,同一个事务内用相同条件查询,第二次结果集多出或少了某些行,通常是因为其他事务插入了满足条件的新记录或删除了已有记录。

这三种问题并不是所有业务都不可接受。例如一些实时统计场景可能允许短暂读到未提交数据,但资金账户系统通常连脏读都不能出现。事务隔离级别的意义就在于给开发者一个可选范围,在不同业务中根据一致性要求选择合适级别,而不是一律使用最高或最低配置。

二、MySQL 四种隔离级别与查看方式

SQL 标准定义了四种事务隔离级别,MySQL InnoDB 全部支持。读未提交级别最低,允许事务读取其他事务未提交的数据;读已提交只允许读取已提交数据,可以避免脏读;可重复读保证同一个事务反复读取同一行时结果一致,但不解决标准的幻读问题;串行化级别最高,事务之间基本串行执行,通过锁强制避免所有并发异常。InnoDB 默认隔离级别是可重复读,这一点与 Oracle 默认的读已提交不同,从 Oracle 迁移到 MySQL 的团队尤其要注意。

InnoDB 实现隔离级别并不单纯依赖行锁,还大量使用 MVCC 多版本并发控制。每个事务在开始时会生成一个一致性快照,普通 SELECT 语句读取快照数据,因此不会阻塞写操作,写操作也不会阻塞普通读。只有更新、删除当前数据时才会涉及行锁和间隙锁。对于可重复读,InnoDB 通过间隙锁在很大程度上也避免了幻读,所以它的可重复读比 SQL 标准中的可重复读更严格一些。

下面的 SQL 可以查看当前会话和全局隔离级别,并在会话级别切换:

-- 查看当前会话的事务隔离级别
SELECT @@transaction_isolation;

-- 查看全局默认隔离级别
SELECT @@global.transaction_isolation;

-- 设置当前会话隔离级别为读已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

这里的 transaction_isolation 适用于较新的 MySQL 版本,老版本中对应变量是 tx_isolation。设置会话级别后,只对当前连接生效,新的连接仍会继承全局默认值。

三、用同一张表观察隔离级别差异

先创建一张简单的账户表,方便后续两个会话模拟并发操作:

CREATE TABLE account (
  id INT PRIMARY KEY,
  balance DECIMAL(10,2)
);

INSERT INTO account VALUES (1, 100.00);

1. 读未提交:看到未提交的修改

会话 A 将隔离级别设置为读未提交,开启事务后把余额改为 200,但不提交。此时会话 B 在相同级别下查询,能够直接读到 200。随后会话 A 执行回滚,余额恢复为 100,会话 B 之前读到的 200 就成为脏数据。这个现象就是脏读。

-- 会话 A
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE account SET balance = 200 WHERE id = 1;
-- 此时还未提交

-- 会话 B
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM account WHERE id = 1;
-- 返回 200

-- 会话 A 回滚
ROLLBACK;

-- 会话 B 再次查询
SELECT balance FROM account WHERE id = 1;
-- 返回 100,之前的 200 是脏数据

读未提交几乎无法用于严肃业务,它的主要问题是让事务看到了不该看到的中间状态。即使后续没有回滚,这种读也可能因为更新顺序混乱产生错误判断。唯一优势是读取开销小、阻塞少,但代价过高。

2. 读已提交:避免脏读,但不可重复读仍存在

将两个会话设置为读已提交。会话 A 第一次查询余额为 100,会话 B 随后把余额改成 200 并提交。会话 A 再次执行相同查询时得到 200。虽然第二次读到的 200 是已提交数据,不是脏数据,但在同一个事务内两次读取结果不同,这就是不可重复读。

-- 会话 A
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
-- 第一次得到 100

-- 会话 B 更新并提交
START TRANSACTION;
UPDATE account SET balance = 200 WHERE id = 1;
COMMIT;

-- 会话 A 再次读取
SELECT balance FROM account WHERE id = 1;
-- 第二次得到 200,发生不可重复读
COMMIT;

读已提交是很多关系型数据库的默认级别,它通过只读取已提交数据来避免脏读。对于某些以短事务为主、业务逻辑能容忍前后读取差异的场景,这个级别能减少长事务持有锁的时间。但如果事务内有先读后判断再写的逻辑,不可重复读可能导致判断条件失效。

3. 可重复读:事务内读取保持一致

在可重复读级别下,会话 A 第一次读取余额为 100,会话 B 即使提交了把余额改为 300 的操作,会话 A 再次读取仍然是 100。这是因为会话 A 使用了事务开始时的一致性快照,普通的 SELECT 读取的是快照版本,而不是最新提交版本。

-- 会话 A
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
-- 第一次得到 100

-- 会话 B 更新并提交
START TRANSACTION;
UPDATE account SET balance = 300 WHERE id = 1;
COMMIT;

-- 会话 A 再次读取
SELECT balance FROM account WHERE id = 1;
-- 仍然得到 100,表现稳定
COMMIT;

需要注意的是,可重复读只对快照读生效。如果事务中执行 SELECT ... FOR UPDATE 或 UPDATE 这类当前读,读取的仍然是最新提交数据。InnoDB 通过行锁和间隙锁保护当前读的范围,这也是它能在可重复读级别下避免大部分幻读的原因。

4. 串行化:读操作也会加锁

串行化是最严格的隔离级别。事务中的普通 SELECT 也会对读取范围加共享锁,其他事务无法修改这些数据,直到当前事务提交或回滚。它基本消除了三种并发异常,但并发能力大幅下降,容易出现锁等待和超时。

-- 会话 A
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
-- 该查询会加共享锁

-- 会话 B 尝试修改同一行,会被阻塞
UPDATE account SET balance = 400 WHERE id = 1;
-- 等待会话 A 提交后才能执行

-- 会话 A 提交后,会话 B 继续执行
COMMIT;

四、工程中如何选择隔离级别

选择隔离级别没有绝对标准,核心是衡量业务能接受多大程度的数据一致性偏差,以及系统能承受多大的锁冲突。MySQL InnoDB 的默认可重复读对大多数业务是稳妥选择,它既能避免脏读和不可重复读,又通过 MVCC 和间隙锁控制了一部分幻读问题。对于读多写少、事务时间较长的应用,可重复读让普通读不阻塞写,表现较好。

如果系统并发压力大,大量事务只做简单更新和读取,并且可以将判断逻辑放到提交后再次校验或使用乐观锁解决不可重复读,那么读已提交也是一种常见选择。读已提交可以减少间隙锁的使用,降低死锁概率。读未提交基本只适合一些不重要的统计或日志类读场景。串行化适合一致性要求极高、数据量不大、并发很低的情况,因为它靠牺牲并发换取最强隔离。

无论选择哪种级别,都要确保事务尽可能短小、更新条件尽量走索引、避免在事务中执行长时间的外部调用。隔离级别解决了部分并发问题,但不能替代良好的表结构设计和索引设计。实际业务中如果遇到诡异的读写不一致,第一步不是直接调高隔离级别,而是先明确当前会话和全局隔离级别设置,再结合具体 SQL 和提交时序复现问题。

  • 保留默认可重复读,适合多数在线交易场景。
  • 读已提交适合更高并发且能接受逻辑校验补偿的系统。
  • 读未提交不要用于核心业务。
  • 串行化只在强制一致性要求下使用,并做好锁等待监控。

MySQL事务隔离级别脏读不可重复读修改时间:2026-10-06 23:14:49

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