灯下哥谭 灯下哥谭
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

灯下哥谭

灯还亮着
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL

    • MySQL 运维知识地图:从入门配置到高可用排障
    • MySQL8 配置文件 my.cnf 重要参数解读
    • MySQL 导出 CSV 中文乱码:字符集链路从头讲一遍
    • MySQL 角色管理
    • MySQL网络抓包审计
    • MySQL 性能压测:Sysbench 1.0 实战
    • MySQL Router 实现读写分离
    • Gh-ost重建表,清除表碎片率
    • MySQL MGR配合MySQL-router实现innodb-cluster
    • MySQL 快速分析binlog定位问题
    • MySQL执行计划分析
    • DBA常用SQL和命令整理备查
    • 单表数据同步方案选型:为什么不该用 mysqldump 做「实时同步」
    • MySQL的事务隔离级别
      • 事务的四大特性(ACID)
      • 并发事务可能出现的问题
        • 1. 脏读(Dirty Read)
        • 2. 不可重复读(Non-Repeatable Read)
        • 3. 幻读(Phantom Read)
        • 不可重复读 vs 幻读的区别
      • MySQL的四种隔离级别
      • 隔离级别的实现原理
      • 如何查看和设置隔离级别
        • 查看当前隔离级别
        • 设置隔离级别
      • 验证:亲手跑一遍再下结论
      • 坑与边界
    • MySQL存储过程批量生成数据
    • MySQL insert on duplicate key update,replace into , insert ignore的理解
    • MySQL不同字符集之间的区别和选择
    • MySQL为什么有时候会选错索引
    • MySQL死锁问题
    • MySQL使用SQL语句查重去重
    • MySQLdump逻辑备份
    • MySQL 基于 GTID 主从复制:跳过异常事务的正确姿势
    • MySQL8快速克隆插件使用指南
    • MySQL8双1设置保障安全
    • MySQL锁
    • innodb cluster安装
    • OPTIMIZE TABLE 和 ANALYZE TABLE 的区别:用实测数据说话
    • MySQLReplicaSet 安装
    • MySQL 的 Left join、Right join 和 Inner join 的区别
    • ORDER BY 配合 LIMIT 触发的索引选择陷阱
  • Redis

  • 高性能KV

  • TiDB

  • Elasticsearch

  • 数据管道

  • 其他数据库

  • 数据库
  • MySQL
灯下哥谭
2022-09-12
目录

MySQL的事务隔离级别

# MySQL的事务隔离级别

MySQL将事务分为不同的隔离级别,主要是为了解决在多事务并发场景下可能出现的数据一致性问题。通过不同级别的事务隔离,可以防止出现脏读、不可重复读和幻读等问题。

版本说明

本文写于 2022-09,基于 MySQL 8.0 InnoDB。
四种隔离级别的语义与 InnoDB 默认的可重复读(RR)+ MVCC/间隙锁实现机制未变,经 2026-07 复核仍适用。

# 事务的四大特性(ACID)

  1. 原子性(Atomicity):事务是不可分割的工作单位,事务中的操作要么全部成功,要么全部失败回滚。

  2. 一致性(Consistency):事务执行前后,数据库必须保持一致性状态。例如:A和B账户共有5000元,不管他们之间如何转账,事务结束后两账户的总金额应该仍然是5000元。

  3. 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

  4. 持久性(Durability):一个事务一旦提交,其对数据库的修改就是永久性的,即使系统发生故障也不会丢失。

# 并发事务可能出现的问题

# 1. 脏读(Dirty Read)

当一个事务读取到另一个事务未提交的数据时,就会发生脏读。

示例:

  • 事务A读取了事务B未提交的数据(余额100修改为200)
  • 事务B随后回滚(余额恢复为100)
  • 事务A使用了错误的数据(认为余额是200)

# 2. 不可重复读(Non-Repeatable Read)

在同一事务内,多次读取同一数据却得到不同的结果。

示例:

  • 事务A第一次读取某行数据
  • 事务B修改该行数据并提交
  • 事务A再次读取该行数据,发现数据已变化

# 3. 幻读(Phantom Read)

在同一事务内,多次查询某个范围的记录,发现记录数量不一致。

示例:

  • 事务A查询工资大于1000的员工数量为10人
  • 事务B插入一条工资为1500的记录并提交
  • 事务A再次查询,发现工资大于1000的员工变为11人

# 不可重复读 vs 幻读的区别

  • 不可重复读:针对同一行被别的事务改掉(UPDATE),两次读值不同
    • 解决方案:MVCC 快照读,或对该行加锁(FOR UPDATE)
  • 幻读:针对同一范围内多出/少了行(INSERT / DELETE),两次读行数不同
    • 解决方案:间隙锁 / next-key lock(InnoDB 的做法),或提到 SERIALIZABLE
    • ⚠️ 常见错误说法是「幻读靠表级锁解决」——InnoDB 从不为此加表锁,它锁的是索引记录之间的间隙,代价比表锁小得多

# MySQL的四种隔离级别

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED(读未提交) 可能 可能 可能
READ COMMITTED(读已提交) 不可能 可能 可能
REPEATABLE READ(可重复读) 不可能 不可能 可能
SERIALIZABLE(串行化) 不可能 不可能 不可能

⚠️ 这张表是 SQL 标准的定义,不是 InnoDB 的实际行为。标准允许 RR 出现幻读,但 InnoDB 在 RR 下已经把幻读堵住了——这是面试里最高频的追问点,只背这张表会当场答错。

MySQL 默认的隔离级别是 REPEATABLE READ(可重复读),InnoDB 在这一级用两套机制分别对付两种读:

读的类型 语句 靠什么防幻读
快照读 普通 SELECT MVCC。事务第一条 SELECT 时生成 Read View 并在整个事务内复用,之后别人插入的行根本不在这个视图里,自然看不见
当前读 SELECT ... FOR UPDATE / FOR SHARE、UPDATE、DELETE next-key lock(行锁 + 间隙锁)。把扫描范围内的间隙一并锁住,别的事务插不进来

所以对 InnoDB 更准确的表是这样:

隔离级别 脏读 不可重复读 幻读(InnoDB 实际)
READ UNCOMMITTED 可能 可能 可能
READ COMMITTED 不可能 可能 可能
REPEATABLE READ 不可能 不可能 基本不可能(见下面的例外)
SERIALIZABLE 不可能 不可能 不可能

唯一还能构造出幻读的口子:在同一个事务里混用快照读和当前读。快照读看的是事务开始时的视图,当前读看的是最新已提交数据,两者不一致:

-- Session A
BEGIN;
SELECT * FROM t WHERE id > 10;            -- 快照读,返回 0 行

-- Session B
INSERT INTO t VALUES (11, 'x'); COMMIT;

-- Session A(同一事务内)
SELECT * FROM t WHERE id > 10;            -- 快照读,仍然 0 行 ✅ 不幻读
SELECT * FROM t WHERE id > 10 FOR UPDATE; -- 当前读,返回 1 行 ← 这就是"幻读"
UPDATE t SET name='y' WHERE id > 10;      -- 同理,影响 1 行
1
2
3
4
5
6
7
8
9
10
11

Session B 的 INSERT 之所以能成功,是因为 Session A 一开始只做了快照读、没有加任何锁。如果 A 第一条就用 FOR UPDATE,next-key lock 会把 id > 10 的间隙锁住,B 的 INSERT 会被阻塞,幻读也就不存在了。

一句话结论:InnoDB 的 RR 下,纯快照读不会幻读,纯当前读也不会幻读,只有两者混用才会。

# 隔离级别的实现原理

MySQL通过多版本并发控制(MVCC)和锁机制来实现事务隔离:

  1. MVCC(Multi-Version Concurrency Control):

    • 每次更新操作都会生成一条回滚日志(undo log)
    • 不同时间点启动的事务会创建不同的read-view
    • 通过回滚日志可以找到数据的历史版本
  2. 回滚日志(undo log)管理:

    • 当没有事务需要用到某个回滚日志时,该日志会被删除
    • 系统中不存在比该回滚日志更早的read-view时,日志即可删除

# 如何查看和设置隔离级别

# 查看当前隔离级别

方式1:

SHOW VARIABLES LIKE 'transaction_isolation';
1

方式2:

SELECT @@transaction_isolation;
1

# 设置隔离级别

  1. 通过SET命令:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;

-- level可选值:
-- REPEATABLE READ
-- READ COMMITTED
-- READ UNCOMMITTED
-- SERIALIZABLE
1
2
3
4
5
6
7
  1. 通过启动参数:
--transaction-isolation=REPEATABLE-READ
1

不同作用域说明:

  • GLOBAL:影响后续新的会话
  • SESSION:影响当前会话的后续事务
  • 无关键词:只影响下一个事务

# 验证:亲手跑一遍再下结论

开两个终端连同一个实例,确认 RR 下快照读到底会不会幻读。

Session A:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT COUNT(*) FROM t WHERE id > 10;
1
2
3

预期输出:

+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1
2
3
4
5

Session B(不要关掉 A 的事务):

INSERT INTO t VALUES (11, 'x');
COMMIT;
1
2

回到 Session A,仍在同一个事务里:

SELECT COUNT(*) FROM t WHERE id > 10;             -- 快照读
SELECT COUNT(*) FROM t WHERE id > 10 FOR UPDATE;  -- 当前读
1
2

预期输出(两条结果不同,这就是上文说的那个口子):

+----------+
| COUNT(*) |
+----------+
|        0 |     ← 快照读,看不到 B 插的行
+----------+
+----------+
| COUNT(*) |
+----------+
|        1 |     ← 当前读,看到了
+----------+
1
2
3
4
5
6
7
8
9
10

再确认一次隔离级别确实是 RR,别在 RC 下白测:

SELECT @@transaction_isolation;
1

# 坑与边界

  • Read View 是在第一条快照读时创建的,不是 BEGIN 时。BEGIN 之后隔了很久才发第一条 SELECT,中间别人提交的数据你是看得到的。要在事务起点就固定视图,用 START TRANSACTION WITH CONSISTENT SNAPSHOT。
  • RR 下的长事务会拖住 undo 不能回收。因为老的 Read View 还需要历史版本,undo 表空间会一路涨,information_schema.INNODB_TRX 里看到跑了几小时的事务就要警觉——这是 RR 最常见的运维代价。
  • RR 的间隙锁是死锁的主要来源。线上如果大量因间隙锁死锁,把隔离级别降到 RC 是常见解法(RC 没有间隙锁),但要先确认业务能接受不可重复读,且 RC 下 binlog 不能用 STATEMENT 格式(会导致主从数据不一致),必须是 ROW。
  • SET GLOBAL 改隔离级别对已建立的连接无效,只影响之后新建的会话;连接池里的老连接会一直用旧值,改完记得让连接池重建连接。
  • 不是所有引擎都吃这一套:隔离级别与 MVCC 是 InnoDB 的实现,MyISAM 没有事务,隔离级别对它完全无意义。

锁的具体形态见 MySQL锁,死锁处置见 MySQL死锁问题。

#锁与并发#MySQL
上次更新: 9/11/2026

← 单表数据同步方案选型:为什么不该用 mysqldump 做「实时同步」 MySQL存储过程批量生成数据→

最近更新
01
DeepSeek Harness 实战 06|学习笔记:插件、工具、技能不在同一个维度上 原创
09-11
02
DeepSeek Harness 实战 05|让两个编码 Agent 共用一份长期记忆 原创
09-09
03
DeepSeek Harness 实战 04|学习笔记:从「已知限制」里读出三处设计张力 原创
09-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式