MySQL 事务隔离级别

事务隔离级别决定了事务之间的可见性,是并发控制的核心机制

事务的 ACID 特性

特性 说明 实现机制
原子性(Atomicity) 事务要么全部成功,要么全部失败 undo log
一致性(Consistency) 事务前后数据保持一致状态 由 A、I、D 共同保证
隔离性(Isolation) 并发事务之间互不干扰 锁 + MVCC
持久性(Durability) 事务提交后永久保存 redo log

并发事务问题

1. 脏读

定义:读到了其他事务未提交的数据。

sql 复制代码
-- 时间线演示
时间    事务A                           事务B
----    ----------------               ----------------
T1      BEGIN;
T2                                      BEGIN;
T3      UPDATE user SET age=20 
        WHERE id=1;  -- age 从 18 改为 20
T4                                      SELECT age FROM user 
                                        WHERE id=1;  -- 读到 20(脏读!)
T5      ROLLBACK;  -- 回滚,age 恢复为 18
T6                                      -- 事务B 拿到的 20 是无效数据

危害:业务逻辑基于错误数据执行,导致数据不一致。

2. 不可重复读

定义 :同一事务内,两次读取同一数据结果不同(针对修改/删除)。

sql 复制代码
时间    事务A                           事务B
----    ----------------               ----------------
T1      BEGIN;
T2      SELECT age FROM user 
        WHERE id=1;  -- 读到 age=18
T3                                      BEGIN;
T4                                      UPDATE user SET age=20 
                                        WHERE id=1;
T5                                      COMMIT;
T6      SELECT age FROM user 
        WHERE id=1;  -- 读到 age=20(不可重复读!)
T7      -- 同一事务两次查询结果不一致

3. 幻读

定义 :同一事务内,两次读取的记录数不同(针对插入)。

sql 复制代码
时间    事务A                           事务B
----    ----------------               ----------------
T1      BEGIN;
T2      SELECT * FROM user 
        WHERE age > 18;  -- 查到 2 条记录
T3                                      BEGIN;
T4                                      INSERT INTO user(age) 
                                        VALUES(25);  -- 插入新记录
T5                                      COMMIT;
T6      SELECT * FROM user 
        WHERE age > 18;  -- 查到 3 条记录(幻读!)
T7      -- 多出来一条"幻影"记录

不可重复读 vs 幻读

  • 不可重复读:侧重于数据内容变化(修改、删除)
  • 幻读:侧重于数据条数变化(插入)

四种隔离级别

隔离级别 脏读 不可重复读 幻读 并发性能
读未提交(RU) 最高
读已提交(RC)
可重复读(RR) ✗*
串行化 最低

✗ 表示可能发生,✓ 表示已解决

MySQL InnoDB 默认使用 RR 级别,并通过 MVCC + 间隙锁解决了幻读问题

各级别详解

1. 读未提交(Read Uncommitted)

复制代码
事务A                          事务B
  │                              │
  │  修改 id=1 的值              │
  │  (未提交)                    │
  │                              │
  │                     读取 id=1 ✓ 能读到未提交的数据!
  │                              │
  │  回滚                        │
  ▼                              ▼

问题:事务B 读到了"脏数据"

特点

  • 几乎不加锁,性能最高
  • 数据一致性最差
  • 生产环境几乎不用

2. 读已提交(Read Committed)

复制代码
事务A                          事务B
  │                              │
  │  修改 id=1 的值              │
  │  (未提交)                    │
  │                              │
  │                     读取 id=1 → 读到旧值 ✓
  │                              │
  │  提交                        │
  │                              │
  │                     读取 id=1 → 读到新值 ✗ 两次读取不一致!
  ▼                              ▼

问题:解决了脏读,但存在不可重复读

特点

  • Oracle、PostgreSQL 默认级别
  • 每次查询生成新的 Read View
  • 适合需要看到最新数据的场景

3. 可重复读(Repeatable Read)

复制代码
事务A                          事务B
  │                              │
  │                     读取 id=1 → 值=100
  │                              │
  │  修改 id=1 为 200            │
  │  提交                        │
  │                              │
  │                     读取 id=1 → 值=100 ✓ 仍然读到旧值
  ▼                              ▼

原理:MVCC 读取快照版本

特点

  • MySQL InnoDB 默认级别
  • 事务开始时生成 Read View,整个事务期间复用
  • 通过间隙锁解决幻读问题

4. 串行化(Serializable)

复制代码
事务A                          事务B
  │                              │
  │  查询 id=1 (加共享锁)         │
  │                              │
  │                     修改 id=1 → 阻塞等待
  │                              │
  │  提交                        │
  │                              │
  │                     获得锁,执行修改
  ▼                              ▼

原理:读写都加锁,完全串行执行

特点

  • 最高隔离级别,完全解决并发问题
  • 性能最差,并发度最低
  • 适用于对一致性要求极高的场景

MVCC 实现原理

核心概念

MVCC(Multi-Version Concurrency Control,多版本并发控制)通过保存数据的历史版本,实现非阻塞读。

隐藏字段

每行数据自动添加三个隐藏字段:

字段 说明
DB_TRX_ID 最后修改该行的事务ID(6字节)
DB_ROLL_PTR 指向 undo log 的指针(7字节)
DB_ROW_ID 行ID,用于索引(6字节,可选)

版本链

复制代码
┌─────────────────────────────────────────────────────┐
│  当前数据 (trx_id=101)                               │
│  id=1, name="李四"                                  │
│  roll_pointer ──────────────────────────┐           │
└──────────────────────────────────────────│───────────┘
                                           ▼
                        ┌─────────────────────────────────┐
                        │  undo log 版本1 (trx_id=100)    │
                        │  name="张三"                    │
                        │  roll_pointer ─────┐            │
                        └────────────────────│────────────┘
                                             ▼
                        ┌─────────────────────────────────┐
                        │  undo log 版本2 (trx_id=50)     │
                        │  name="王五"                    │
                        │  roll_pointer = null            │
                        └─────────────────────────────────┘

Read View 结构

java 复制代码
class ReadView {
    // 当前活跃事务ID列表
    List<Long> m_ids;           // [100, 101, 102]
    
    // 最小活跃事务ID
    long min_trx_id;            // 100
    
    // 下一个要分配的事务ID
    long max_trx_id;            // 103
    
    // 当前事务ID
    long creator_trx_id;        // 99
}

可见性判断

复制代码
遍历版本链,判断每个版本是否可见:

1. trx_id == creator_trx_id
   → 自己修改的,可见 ✓

2. trx_id < min_trx_id
   → 版本在当前事务之前已提交,可见 ✓

3. trx_id >= max_trx_id
   → 版本是将来事务产生的,不可见 ✗

4. trx_id 在 m_ids 中
   → 版本是未提交事务产生的,不可见 ✗

5. trx_id 不在 m_ids 中
   → 版本已提交,可见 ✓

RC 和 RR 的区别

级别 Read View 生成时机 效果
RC 每次查询都生成新的 能看到最新提交的数据
RR 事务开始时生成一次 整个事务看到相同的数据快照
复制代码
RC 级别:
查询1 → 生成 ReadView1 → 看到版本A
其他事务提交新版本
查询2 → 生成 ReadView2 → 看到版本B(新版本)

RR 级别:
查询1 → 生成 ReadView → 看到版本A
其他事务提交新版本
查询2 → 复用 ReadView → 仍然看到版本A

快照读与当前读

快照读

读取数据的历史版本,不加锁。

sql 复制代码
-- 普通 SELECT 就是快照读
SELECT * FROM user WHERE id = 1;

当前读

读取数据的最新版本,加锁。

sql 复制代码
-- 共享锁(S锁)
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;
SELECT * FROM user WHERE id = 1 FOR SHARE;  -- MySQL 8.0+

-- 排他锁(X锁)
SELECT * FROM user WHERE id = 1 FOR UPDATE;

-- 以下操作也是当前读
UPDATE user SET name = 'test' WHERE id = 1;
DELETE FROM user WHERE id = 1;
INSERT INTO user VALUES (1, 'test');

对比

类型 读取版本 是否加锁 适用场景
快照读 历史版本 普通查询
当前读 最新版本 更新、删除、加锁查询

间隙锁详解

什么是间隙锁

间隙锁(Gap Lock)锁定一个范围,防止其他事务在这个范围内插入数据。

复制代码
假设表中有数据:id = 1, 5, 10

间隙锁锁定的范围:
(-∞, 1]  (1, 5]  (5, 10]  (10, +∞)
   ↑        ↑        ↑         ↑
 间隙1    间隙2    间隙3      间隙4

解决幻读

sql 复制代码
-- 事务A
BEGIN;
SELECT * FROM user WHERE id > 5 FOR UPDATE;
-- 锁住 (5, +∞) 范围

-- 事务B
INSERT INTO user VALUES (8, 'test');  -- 阻塞!无法插入
INSERT INTO user VALUES (3, 'test');  -- 成功,不在锁定范围

间隙锁类型

类型 说明
Gap Lock 只锁间隙,不锁记录
Record Lock 只锁记录,不锁间隙
Next-Key Lock Record Lock + Gap Lock,锁记录+前间隙
复制代码
Next-Key Lock 示例:

数据:id = 1, 5, 10

SELECT * FROM user WHERE id = 5 FOR UPDATE;

锁定的范围:(1, 5]  -- Next-Key Lock
            ↑    ↑
          间隙   记录

设置隔离级别

查看隔离级别

sql 复制代码
-- MySQL 5.7+
SELECT @@transaction_isolation;

-- MySQL 5.6 及之前
SELECT @@tx_isolation;

-- 查看全局和会话级别
SELECT @@global.transaction_isolation, @@session.transaction_isolation;

设置隔离级别

sql 复制代码
-- 设置会话级别(仅当前连接有效)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局级别(新连接生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 设置下一个事务的隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

配置文件设置

ini 复制代码
[mysqld]
transaction-isolation = REPEATABLE-READ

隔离级别选择

各级别适用场景

隔离级别 适用场景 典型应用
RU 几乎不用 日志分析等非关键业务
RC 需要实时数据、高并发 电商商品展示、社交媒体
RR 默认选择,通用场景 大多数业务系统
Serializable 强一致性要求 金融交易、库存扣减

性能对比

复制代码
并发性能:RU > RC > RR > Serializable

原因:
- RU:几乎不加锁
- RC:MVCC,每次查询生成 Read View
- RR:MVCC + 间隙锁
- Serializable:完全加锁串行

常见问题

Q1: 为什么 MySQL 默认用 RR 而不是 RC?

  1. 数据一致性更好:RR 解决了不可重复读问题
  2. 幻读问题已解决:InnoDB 通过 MVCC + 间隙锁解决了幻读
  3. 主从复制友好:基于语句的复制在 RR 下更安全
  4. 历史原因:MySQL 早期版本就默认 RR

Q2: RR 如何解决幻读?

sql 复制代码
-- 快照读:通过 MVCC 解决
SELECT * FROM user WHERE id > 5;
-- 读取事务开始时的快照,看不到新插入的数据

-- 当前读:通过间隙锁解决
SELECT * FROM user WHERE id > 5 FOR UPDATE;
-- 锁住 (5, +∞) 范围,其他事务无法插入

Q3: 什么时候用 RC?

  1. 需要看到最新数据:如商品库存实时展示
  2. 高并发场景:减少间隙锁带来的锁等待
  3. 业务容忍不可重复读:两次查询结果不一致不影响业务

Q4: RC 和 RR 的锁区别?

方面 RC RR
间隙锁
Next-Key Lock
锁范围 只锁匹配行 锁行 + 间隙
并发度 更高 较低

总结

隔离级别选择原则

复制代码
在满足业务需求的前提下,选择并发性能最高的隔离级别

快速选择指南

场景 推荐级别 原因
默认选择 RR 平衡一致性和性能
高并发读、低一致性要求 RC 减少锁竞争
金融交易、强一致性 Serializable 最高隔离级别
几乎不用 RU 数据一致性太差

核心要点

  1. 理解三种问题:脏读、不可重复读、幻读
  2. 掌握 MVCC:版本链、Read View、可见性判断
  3. 区分两种读:快照读(MVCC)、当前读(加锁)
  4. 合理选择级别:根据业务需求权衡一致性和性能
相关推荐
Je1lyfish2 小时前
CMU15-445 (2026 Spring) Project#1 - Buffer Pool Manager
linux·数据库·c++·后端·链表·课程设计·数据库架构
hrhcode2 小时前
【Netty】三.ChannelPipeline与ChannelHandler责任链深度解析
java·后端·spring·springboot·netty
Re.不晚2 小时前
Redis——哨兵机制
数据库·redis·bootstrap
代码星辰2 小时前
MySQL 面试题——深度分页优化
数据库·mysql·覆盖索引·深度分页
散装DBA2 小时前
OpenClaw+钉钉机器人实现数据库操作
数据库·机器人·钉钉
invicinble2 小时前
关于学习技术栈的思考
java·开发语言·学习
哈库纳玛塔塔3 小时前
公元前日期处理的两种方案
数据库·算法·mybatis
json{shen:"jing"}3 小时前
分割回文串-暴力法
java·算法
没有bug.的程序员3 小时前
Maven 进阶进阶:依赖优化内核、多模块构建艺术与“依赖地狱”自愈指南
java·maven·构建·多模块·依赖优化