乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

事务隔离级别

导学

多个事务同时操作数据库时,会出现脏读、不可重复读、幻读等并发问题。隔离级别定义了一个事务对其他事务的"可见程度",在一致性和并发性能之间做权衡。

定义

事务隔离级别(Transaction Isolation Level):定义事务之间隔离程度的标准。SQL 标准定义了 4 个隔离级别,MySQL 5.7 的 InnoDB 默认使用可重复读(REPEATABLE READ)。

并发问题速查

问题描述
脏读(Dirty Read)读到其他事务未提交的数据
不可重复读(Non-repeatable Read)同一事务内两次读同一行,结果不同(被其他事务修改并提交)
幻读(Phantom Read)同一事务内两次查询,结果集行数不同(被其他事务插入或删除)

隔离级别与并发问题

隔离级别脏读不可重复读幻读实现方式
READ UNCOMMITTED✅ 允许✅ 允许✅ 允许不加锁,直接读最新
READ COMMITTED❌ 禁止✅ 允许✅ 允许每次读生成新 ReadView
REPEATABLE READ❌ 禁止❌ 禁止⚠️ 部分允许事务开始时生成 ReadView
SERIALIZABLE❌ 禁止❌ 禁止❌ 禁止所有操作加排他锁

MySQL 5.7 的 InnoDB 在 REPEATABLE READ 下通过 MVCC + Next-Key Lock 基本解决了幻读问题(详见《MVCC》和《锁机制》文档)。

设置隔离级别

示例:查看和设置隔离级别

-- 查看当前隔离级别(MySQL 5.7.20+)
SELECT @@transaction_isolation;

-- 旧版本使用
SELECT @@tx_isolation;

-- 设置会话级隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局隔离级别(需 SUPER 权限,新连接生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 验证修改后的隔离级别
SELECT @@transaction_isolation;

操作前状态: 默认输出:

@@transaction_isolation
REPEATABLE-READ

设置后的结果:

@@transaction_isolation
READ-COMMITTED

结果解读:

  • 会话级设置只影响当前连接
  • 全局设置影响新建立的连接,已有连接不受影响

SQL 示例:各种隔离级别的并发演示

准备测试数据

CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);

employees 表初始数据:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

场景一:脏读(READ UNCOMMITTED)

脏读是指一个事务读到了另一个事务尚未提交的修改。只有在 READ UNCOMMITTED 隔离级别下才会发生。

操作过程:两个会话并发执行

时间顺序会话 A(事务1)会话 B(事务2)
T1SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
T2START TRANSACTION;
T3START TRANSACTION;
T4SELECT score FROM employees WHERE emp_id = 1; -- 结果:100
T5UPDATE employees SET score = 90 WHERE emp_id = 1; -- 未提交
T6SELECT score FROM employees WHERE emp_id = 1; -- 结果:90(脏读!)
T7ROLLBACK; -- 回滚修改
T8SELECT score FROM employees WHERE emp_id = 1; -- 结果:100
T9COMMIT;

完整 SQL 演示:

-- ========== 会话 A ==========
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;

-- 第一次查询:读到原始数据
SELECT score FROM employees WHERE emp_id = 1;

当前数据状态(T4):

score
100
-- 此时切换到会话 B 执行 UPDATE(未提交)
-- ========== 会话 B ==========
START TRANSACTION;
UPDATE employees SET score = 90 WHERE emp_id = 1;
-- 注意:此时不要 COMMIT 或 ROLLBACK

-- ========== 会话 A 继续 ==========
-- 第二次查询:读到了会话 B 未提交的数据(脏读)
SELECT score FROM employees WHERE emp_id = 1;

当前数据状态(T6,脏读):

score
90.00

结果解读:

  • 会话 A 在 T6 读到了 90,但这是会话 B 尚未提交的修改
  • 如果会话 B 最终回滚(T7),这个 90 就是"脏数据",从未真正存在过
  • 这就是脏读:读到了其他事务未提交的、可能回滚的数据
-- 会话 B 回滚
ROLLBACK;

-- ========== 会话 A 继续 ==========
-- 第三次查询:数据恢复为 100
SELECT score FROM employees WHERE emp_id = 1;
COMMIT;

操作后的数据状态(T8):

score
100

场景二:不可重复读(READ COMMITTED)

不可重复读是指同一事务内,两次读取同一行数据,结果不一致(因为其他事务在中间提交了修改)。在 READ COMMITTED 和 READ UNCOMMITTED 下会发生。

操作过程:两个会话并发执行

时间顺序会话 A(事务1,RC级别)会话 B(事务2)
T1SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
T2START TRANSACTION;
T3START TRANSACTION;
T4SELECT score FROM employees WHERE emp_id = 1; -- 结果:100
T5UPDATE employees SET score = 80 WHERE emp_id = 1;
T6COMMIT;
T7SELECT score FROM employees WHERE emp_id = 1; -- 结果:80(不可重复读!)
T8COMMIT;

完整 SQL 演示:

-- ========== 会话 A ==========
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;

-- 第一次查询
SELECT score FROM employees WHERE emp_id = 1;

当前数据状态(T4):

score
100
-- ========== 会话 B ==========
START TRANSACTION;
UPDATE employees SET score = 80 WHERE emp_id = 1;
COMMIT;

-- ========== 会话 A 继续 ==========
-- 第二次查询:读到了会话 B 已提交的修改
SELECT score FROM employees WHERE emp_id = 1;

当前数据状态(T7,不可重复读):

score
80.00

结果解读:

  • 会话 A 在同一事务内,两次查询同一行,结果从 100 变成了 80
  • 虽然 80 是已提交的合法数据,但对会话 A 来说,"同一事务内数据变了",这就是不可重复读
  • 如果会话 A 的业务逻辑依赖"同一事务内数据不变",就会出现问题
-- 会话 A 提交
COMMIT;

-- 验证最终数据
SELECT score FROM employees WHERE emp_id = 1;

操作后的数据状态:

score
80.00

场景三:可重复读(REPEATABLE READ,MySQL 默认)

在 REPEATABLE READ 隔离级别下,同一事务内多次读取同一行数据,结果始终保持一致。InnoDB 通过 MVCC 机制实现这一点。

操作过程:两个会话并发执行

时间顺序会话 A(事务1,RR级别)会话 B(事务2)
T1SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
T2START TRANSACTION;
T3START TRANSACTION;
T4SELECT score FROM employees WHERE emp_id = 1; -- 结果:80
T5UPDATE employees SET score = 60 WHERE emp_id = 1;
T6COMMIT;
T7SELECT score FROM employees WHERE emp_id = 1; -- 结果:80(可重复读!)
T8COMMIT;
T9SELECT score FROM employees WHERE emp_id = 1; -- 结果:60

完整 SQL 演示:

-- ========== 会话 A ==========
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;

-- 第一次查询
SELECT score FROM employees WHERE emp_id = 1;

当前数据状态(T4):

score
80.00
-- ========== 会话 B ==========
START TRANSACTION;
UPDATE employees SET score = 60 WHERE emp_id = 1;
COMMIT;

-- ========== 会话 A 继续 ==========
-- 第二次查询:仍然读到 80,不受会话 B 提交的影响
SELECT score FROM employees WHERE emp_id = 1;

当前数据状态(T7,可重复读):

score
80.00

结果解读:

  • 会话 B 已经提交,将 score 修改为 60
  • 但会话 A 在同一事务内再次查询,仍然读到 80
  • 这是 InnoDB MVCC 机制的效果:事务 A 看到的是事务开始时的快照,不受其他事务提交的影响
  • 只有当事务 A 结束(COMMIT 或 ROLLBACK)后,新事务才能看到 60
-- 会话 A 提交
COMMIT;

-- 新查询:现在可以看到最新数据
SELECT score FROM employees WHERE emp_id = 1;

操作后的数据状态(T9):

score
60

场景四:幻读演示

幻读是指同一事务内,两次执行相同条件的范围查询,结果集的行数不同。在标准的 REPEATABLE READ 下,MVCC 只能解决快照读的幻读;当前读(FOR UPDATE)可能遇到幻读,但在 MySQL 5.7 中通过 Gap Lock 基本解决了。

操作过程:快照读下的幻读防护

时间顺序会话 A(事务1,RR级别)会话 B(事务2)
T1START TRANSACTION;
T2SELECT * FROM employees WHERE score > 50; -- 1行
T3START TRANSACTION;
T4INSERT INTO employees VALUES (3, '孔蓝', '产品部', 70);
T5COMMIT;
T6SELECT * FROM employees WHERE score > 50; -- 仍然1行(MVCC防护)
T7COMMIT;

完整 SQL 演示:

-- ========== 会话 A ==========
START TRANSACTION;

-- 第一次范围查询
SELECT * FROM employees WHERE score > 50;

当前数据状态(T2):

emp_idemp_namedeptscore
1大翔技术部100
-- ========== 会话 B ==========
START TRANSACTION;
INSERT INTO employees (emp_name, dept, score) VALUES ('孔蓝', '产品部', 70);
COMMIT;

-- ========== 会话 A 继续 ==========
-- 第二次范围查询:在 RR 隔离级别下,结果集仍然是 1 行
SELECT * FROM employees WHERE score > 50;

当前数据状态(T6,快照读无幻读):

emp_idemp_namedeptscore
1大翔技术部100

结果解读:

  • 会话 B 插入了一行 score = 70 的新记录并提交
  • 但会话 A 在 RR 级别下的普通 SELECT(快照读)仍然只看到 1 行
  • 这是因为 MVCC 机制让会话 A 看到事务开始时的快照,新插入的行对会话 A 不可见
-- 会话 A 提交后再查询
COMMIT;
SELECT * FROM employees WHERE score > 50;

操作后的数据状态(新事务):

emp_idemp_namedeptscore
1大翔技术部100
3孔蓝产品部70

注:演示后执行 DELETE FROM employees WHERE emp_name = '孔蓝'; 恢复为 2 条数据。

当前读下的幻读与 Gap Lock 防护

-- ========== 会话 A ==========
START TRANSACTION;

-- 当前读(加锁)
SELECT * FROM employees WHERE score > 50 FOR UPDATE;

当前数据状态:同上,1 行

-- ========== 会话 B ==========
START TRANSACTION;
-- 尝试插入 score = 70 的记录
INSERT INTO employees (emp_name, dept, score) VALUES ('黄俪', '产品部', 70);
-- 结果:被阻塞!等待会话 A 释放锁

结果解读:

  • 会话 A 使用 FOR UPDATE 进行当前读,InnoDB 会加 Next-Key Lock(行锁 + 间隙锁)
  • 会话 B 的 INSERT 操作被阻塞,因为间隙锁阻止了在 score > 50 范围内的插入
  • 这就是 MySQL 5.7 InnoDB 在 RR 级别下防止幻读的机制
-- 会话 A 提交后,会话 B 的 INSERT 才能继续
COMMIT;

场景五:SERIALIZABLE 隔离级别

SERIALIZABLE 是最高隔离级别,所有操作都加锁,完全串行化执行。

操作过程

-- ========== 会话 A ==========
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;

SELECT score FROM employees WHERE emp_id = 1;

当前数据状态:

score
60
-- ========== 会话 B ==========
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;

-- 尝试查询同一行
SELECT score FROM employees WHERE emp_id = 1;
-- 在 SERIALIZABLE 下,普通 SELECT 也会加共享锁
-- 但如果会话 A 已经加了锁,这里可能需要等待
-- 会话 A 更新数据
UPDATE employees SET score = 500 WHERE emp_id = 1;
COMMIT;

结果解读:

  • SERIALIZABLE 下,即使是普通 SELECT 也会加共享锁
  • 并发事务对同一行的读写会被严格串行化
  • 这种级别一致性最好,但并发性能最差,生产环境很少使用

隔离级别的选择

场景推荐级别
强一致性要求(金融、库存)REPEATABLE READ 或 SERIALIZABLE
读多写少,允许一定不一致READ COMMITTED
报表、日志分析READ COMMITTED
极高并发,可接受脏读READ UNCOMMITTED(极少使用)

常见误区

误区正解
隔离级别越高越好越高并发性能越差。SERIALIZABLE 基本等同于单线程。
MySQL 的 REPEATABLE READ 有幻读InnoDB 通过 MVCC + Gap Lock 在 RR 级别基本解决了幻读(非完全,特殊场景仍可能出现)。
RC 比 RR 性能一定好RC 减少了锁的持有范围,但某些场景下 RR 的读操作无需加锁(MVCC),反而更高效。

面试考点

Q:MySQL 默认的隔离级别是什么?为什么?

REPEATABLE READ。这是为了兼容早期的 Binlog 格式(Statement-based replication),在 RR 下复制更安全。READ COMMITTED 配合 Row-based replication 也是现代推荐方案。

Q:READ COMMITTED 和 REPEATABLE READ 的区别?

RC 每次查询生成新的 ReadView,能看到其他事务已提交的修改,存在不可重复读;RR 在事务开始时生成 ReadView,事务内始终看到同一快照,避免不可重复读。

Q:幻读和不可重复读的区别?

不可重复读是同一行数据被修改,两次读取内容不同;幻读是结果集的行数发生变化(新增或删除了符合条件的行)。

小结

  • 四种隔离级别:RU、RC、RR、SERIALIZABLE
  • MySQL 5.7 InnoDB 默认 RR,通过 MVCC 实现快照读
  • 隔离级别越高,一致性越好,并发性能越差
  • 大多数 OLTP 系统使用 RC 或 RR

下一章引子:隔离性通过锁和 MVCC 共同实现。接下来深入了解 InnoDB 的锁机制。

上一页
事务
下一页
锁机制