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. 合理选择级别:根据业务需求权衡一致性和性能
相关推荐
2301_8135995516 小时前
Go语言怎么做秒杀系统_Go语言秒杀系统实战教程【实用】
jvm·数据库·python
NCIN EXPE21 小时前
redis 使用
数据库·redis·缓存
MongoDB 数据平台21 小时前
为编码代理引入 MongoDB 代理技能和插件
数据库·mongodb
lUie INGA21 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
极客on之路21 小时前
mysql explain type 各个字段解释
数据库·mysql
代码雕刻家21 小时前
MySQL与SQL Server的基本指令
数据库·mysql·sqlserver
lThE ANDE21 小时前
开启mysql的binlog日志
数据库·mysql
yejqvow1221 小时前
CSS如何控制placeholder文字的颜色_使用--placeholder伪元素
jvm·数据库·python
oLLI PILO21 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库
geBR OTTE21 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端