Skip to main content
Documents
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

MySQL 事务

内容覆盖核心概念、事务流程、隔离级别、MVCC、锁关系、死锁、优化实践,全程深入但保持简洁。

一、什么是事务?

事务(Transaction)是 一组要么全部成功、要么全部失败的数据库操作。本质是保证数据一致性的基础机制。

事务具备 ACID 四大特性:

特性 描述
A 原子性(Atomicity) 事务内操作不可分割,要么都执行、要么都不执行(靠 undo log 实现)
C 一致性(Consistency) 事务执行前后数据必须保持一致,不出现非法状态
I 隔离性(Isolation) 多事务之间互不干扰(靠锁、MVCC 实现)
D 持久性(Durability) 提交后数据必须持久保存(靠 redo log+binlog 实现)

二、事务的底层日志体系(关键理解点)

MySQL InnoDB 为事务准备了 3 套关键日志:

1. redo log(重做日志)

  • 保证 持久性
  • 记录“页面修改后的内容”
  • 崩溃恢复后 重做已提交事务的修改
  • WAL 机制:先写日志,再写数据页

2. undo log(回滚日志)

  • 保证 原子性
  • 用于:
    • 回滚事务
    • MVCC 提供“旧版本数据”给快照读

3. binlog(归档日志)

  • 服务于复制、主从同步、备份恢复
  • 记录所有 逻辑变化
  • append-only,不会覆盖

三、事务的两种实现方式(重点)

1. 隐式事务(autocommit = 1)

每条 SQL 都是一个独立事务:

INSERT ...
UPDATE ...

每执行一条就自动提交。

2. 显式事务(推荐)

START TRANSACTION;
UPDATE t SET ...;
COMMIT;

手动控制:

ROLLBACK;

四、事务隔离级别(Isolation Levels)

四个隔离级别:

隔离级别 脏读 不可重复读 幻读 性能
READ UNCOMMITTED 最快
READ COMMITTED 较快
REPEATABLE READ(MySQL 默认) ❌(InnoDB MVCC + Next-key lock) 平衡
SERIALIZABLE 最慢,锁表

五、MySQL 是如何解决并发读写冲突的?

A. MVCC + 快照读(大部分查询)

InnoDB 使用 MVCC(多版本并发控制)解决读写冲突:

  • 不加锁,高性能
  • SELECT 默认使用 快照读
  • 使用 undo log 提供历史版本

例如:

SELECT * FROM t WHERE id = 1;

不会加锁。

B. 加锁读(锁定当前行)

1)共享锁(S)

SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE;

允许别人读,不允许写。

2)排他锁(X)

SELECT * FROM t WHERE id=1 FOR UPDATE;

不允许别人读(快照读除外),也不允许别人写。

六、MVCC(多版本并发控制)详解(核心)

MVCC 依靠 隐藏字段 + undo log + Read View 实现:

InnoDB 每行有 2 个隐藏元数据:

  • trx_id :最近一次修改该行的事务 ID
  • roll_pointer:指向 undo log 中上一版本数据

Read View 决定“可见版本”

创建快照时记录活跃事务列表,判断数据版本是否可见。

因此:

  • 快照读 不会被写阻塞
  • 读写并发大幅提升
  • 幻读被 Next-Key Lock 规避

七、幻读如何被解决?

MySQL 使用:

范围锁(Range Lock)

Next-Key Lock(记录锁 + 间隙锁)

例如:

SELECT * FROM t WHERE age > 20 FOR UPDATE;

会锁住:

  • 已存在行(Record Lock)
  • 20~最大值的区间(Gap Lock)

防止别人插入新行 -> 避免幻读。

八、事务的执行流程(生命周期)

  1. 开始事务
    • 维护一个事务 ID(trx_id)
  2. 执行 SQL -> 写 undo log
  3. 修改 Buffer Pool 内数据页
  4. 写 redo log(prepare 状态)
  5. 提交 COMMIT -> redo log 由 prepare -> commit 状态
  6. binlog 写入成功(两阶段提交)
  7. commit 事务结束

保证崩溃恢复后:

  • redo log 不会丢数据
  • binlog 精确用于主从复制

九、常见事务问题与示例

1. 脏读(Dirty Read)

读到了“未提交的事务的数据”。

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

同一次事务中,两次 SELECT 返回不同结果。

3. 幻读(Phantom Read)

范围查询时,新插入了记录。

十、死锁(Deadlock)

死锁发生原因

  • 多个事务相互持有对方要的锁
  • 顺序不一致
  • 业务逻辑竞争

解决方法

  • InnoDB 自动检测并回滚一个事务(报 1213)
  • 应用层捕获重试

避免死锁的最佳实践

  • 顺序访问表和行
  • 粗粒度锁(例如一次 FOR UPDATE 取出所有 ID)
  • 缩短事务时间
  • 使用合理索引减少锁定数量
  • 避免在事务中执行外部操作(例如接口调用)

十一、事务最佳实践

  • 使用短事务(避免长事务导致 undo 爆增)
  • 必须有 WHERE,避免锁全表
  • 主键精确更新,减少锁冲突
  • 读多写少业务 -> 使用快照读
  • 分布式业务 -> 规避分布式事务(XA, 2PC)

十二、总结

问题 简要回答
MVCC 的核心是什么? undo log + 隐藏版本号 + Read View
MySQL 如何解决幻读? Next-key Lock
redo log 和 binlog 区别? redo:物理日志;binlog:逻辑日志
两阶段提交为什么需要? 保证 MySQL 崩溃后 binlog 和 redo log 一致
RR 为什么不会出现不可重复读? 快照读 + 版本链
RR 为什么不会出现幻读? Next-key Lock