事务

事务是数据库的核心概念,理解事务的 ACID 特性和隔离级别是掌握 MySQL 的关键。

事务的基本概念

什么是事务?

事务(Transaction)是数据库操作的最小工作单元,是一组不可分割的数据库操作序列。

特点

  • 要么全部成功

  • 要么全部失败

  • 保证数据的一致性

事务的使用

-- 开启事务
BEGIN;  -- 或 START TRANSACTION;

-- 执行 SQL 操作
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 提交事务
COMMIT;

-- 或者回滚事务
ROLLBACK;

为什么需要事务?

场景:银行转账

使用事务


ACID 特性详解 ⭐⭐⭐⭐⭐

ACID 是事务的四大特性,这是面试必考的核心知识点

A - Atomicity(原子性)

定义

事务是不可分割的最小单位,要么全部成功,要么全部失败。

实现原理:Undo Log

机制

  1. 事务开始时,记录数据的原始值到 Undo Log

  2. 事务执行过程中,数据被修改

  3. 如果需要回滚,使用 Undo Log 恢复数据

示例

Undo Log 的作用

  1. 保证原子性:回滚时恢复数据

  2. 实现 MVCC:提供历史版本(下一章详解)

C - Consistency(一致性)

定义

事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。

什么是"一致性状态"?

数据库层面

  • 满足所有约束(主键、外键、唯一性、检查约束)

  • 满足触发器规则

业务层面

  • 满足业务规则

  • 例如:转账前后总金额不变

实现原理

一致性不是由单一机制保证的,而是由其他三个特性共同实现:

示例

I - Isolation(隔离性)⭐⭐⭐⭐⭐

定义

并发执行的事务之间互不干扰,每个事务都感觉像是独占数据库。

实现原理:锁 + MVCC

锁机制

MVCC(多版本并发控制)

  • 解决读-写冲突

  • 使用 Undo Log 版本链

D - Durability(持久性)⭐⭐⭐⭐⭐

定义

事务一旦提交,对数据库的修改就是永久性的,即使系统崩溃也不会丢失。

实现原理:Redo Log + WAL

WAL(Write-Ahead Logging)机制

为什么这样设计?

操作
直接写磁盘
先写 Redo Log

IO 类型

随机写

顺序写

性能

可靠性

高(持久化到磁盘)

示例流程

Redo Log 的刷盘策略

参数:innodb_flush_log_at_trx_commit

含义
性能
安全性

0

每秒刷一次盘

最快

可能丢失 1 秒数据

1

每次提交都刷盘

最慢

最安全(默认)

2

每次提交写到 OS 缓存

中等

可能丢失数据(OS 崩溃)

推荐配置

ACID 特性总结

特性
定义
实现机制
重要性

Atomicity

全部成功或全部失败

Undo Log

⭐⭐⭐⭐⭐

Consistency

数据保持一致

A + I + D

⭐⭐⭐⭐

Isolation

事务间互不干扰

锁 + MVCC

⭐⭐⭐⭐⭐

Durability

提交后永久保存

Redo Log + WAL

⭐⭐⭐⭐⭐


事务隔离级别 ⭐⭐⭐⭐⭐

隔离级别是面试的绝对高频考点,必须熟练掌握。

四种隔离级别

隔离级别
脏读
不可重复读
幻读

READ UNCOMMITTED (读未提交)

READ COMMITTED (读已提交)

REPEATABLE READ (可重复读,MySQL 默认)

✗*

SERIALIZABLE (串行化)

注意

  • ✓ = 会出现该问题

  • ✗ = 不会出现该问题

  • ✗* = MySQL 的 RR 通过 Next-Key Lock 解决了幻读

设置隔离级别


并发问题详解 ⭐⭐⭐⭐⭐

这是面试必问的内容,必须能用 SQL 演示出来。

1. 脏读(Dirty Read)

定义

一个事务读到了另一个未提交事务修改的数据。

问题

如果那个事务回滚了,读到的就是无效数据。

演示

隔离级别:READ UNCOMMITTED

影响

  • 事务 A 基于 500 做决策

  • 但 500 是无效数据

  • 导致业务逻辑错误

解决方案

使用 READ COMMITTED 或更高的隔离级别。

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

定义

同一个事务内,多次读取同一数据,结果不一致。

原因

其他事务修改并提交了数据。

演示

隔离级别:READ COMMITTED

影响

  • 同一个事务内,读取不一致

  • 无法保证数据稳定性

  • 影响业务逻辑

使用场景

  • 大多数场景可以接受不可重复读

  • 例如:查询账户余额(实时性更重要)

解决方案

使用 REPEATABLE READ 或更高的隔离级别。

3. 幻读(Phantom Read)⭐⭐⭐⭐⭐

定义

同一个事务内,多次查询,返回的结果集不一致。

原因

其他事务插入或删除了数据。

与不可重复读的区别

问题
关注点
原因

不可重复读

单条记录的值变化

其他事务 UPDATE

幻读

结果集的行数变化

其他事务 INSERT/DELETE

演示

隔离级别:READ COMMITTED(没有间隙锁)

更严重的幻读场景

MySQL 的解决方案 ⭐⭐⭐⭐⭐

REPEATABLE READ + Next-Key Lock

MySQL 的 RR 隔离级别通过 Next-Key Lock(临键锁)解决了幻读:

关键点

  1. 当前读(FOR UPDATE)使用 Next-Key Lock 防止幻读

  2. 快照读(普通 SELECT)使用 MVCC 防止幻读

  3. 两种机制配合,彻底解决幻读

4. 丢失更新(Lost Update)

定义

两个事务同时更新同一数据,后提交的事务覆盖了先提交的事务的修改。

演示

解决方案

方案 1:使用锁

方案 2:使用原子操作

方案 3:乐观锁

并发问题总结表 ⭐⭐⭐⭐⭐

问题
定义
原因
隔离级别
解决方案

脏读

读到未提交的数据

其他事务未提交

RU

RC 及以上

不可重复读

同一数据多次读取不一致

其他事务 UPDATE 并提交

RC

RR 及以上

幻读

结果集行数变化

其他事务 INSERT/DELETE

RC

RR(Next-Key Lock)

丢失更新

后提交的覆盖先提交的

并发更新

所有级别

加锁或乐观锁


当前读与快照读 ⭐⭐⭐⭐⭐

这是理解 MySQL 并发控制的关键概念

快照读(Snapshot Read)

定义

读取的是数据的历史版本(快照),不是最新版本。

特点

  • 不加锁

  • 使用 MVCC 实现

  • 读取的是事务开始时的快照

SQL 语句

示例

当前读(Current Read)

定义

读取的是数据的最新版本(当前值)。

特点

  • 会加锁

  • 读取的是最新数据

  • 使用锁机制防止并发修改

SQL 语句

示例

快照读 vs 当前读

特性
快照读
当前读

读取版本

历史版本(快照)

最新版本

是否加锁

不加锁

加锁

实现机制

MVCC

并发性能

SQL 示例

SELECT ...

SELECT ... FOR UPDATE

幻读的完整解决方案 ⭐⭐⭐⭐⭐

MySQL 的 RR 隔离级别通过两种机制解决幻读:

1. 快照读:MVCC

2. 当前读:Next-Key Lock

总结


日志系统 ⭐⭐⭐⭐⭐

MySQL 有三种重要的日志,它们是实现事务 ACID 特性的基础。

1. Redo Log(重做日志)⭐⭐⭐⭐⭐

作用

保证事务的持久性(Durability)。

工作原理

WAL(Write-Ahead Logging)机制

为什么不直接写数据页?

操作
写 Redo Log
写数据页

IO 类型

顺序写

随机写

大小

小(只记录修改)

大(整页 16KB)

性能

示例

Redo Log 的格式

崩溃恢复

两阶段提交(2PC)⭐⭐⭐⭐⭐

为什么需要两阶段提交?

保证 Redo LogBinlog 的一致性。

流程

异常场景分析

为什么这样设计?

参数配置

2. Undo Log(回滚日志)⭐⭐⭐⭐⭐

作用

  1. 保证事务的原子性(Atomicity)

  2. 实现 MVCC(多版本并发控制)

工作原理

记录数据的旧值

回滚操作

Undo Log 版本链

形成历史版本链

这是 MVCC 的基础,详见 MVCC 文档

Undo Log 的类型

  1. Insert Undo Log

    • INSERT 操作产生

    • 只在事务回滚时需要

    • 事务提交后立即删除

  2. Update Undo Log

    • UPDATE 和 DELETE 操作产生

    • 事务提交后不能立即删除

    • MVCC 可能需要读取历史版本

    • 由 Purge 线程清理

Purge 操作

清理不再需要的 Undo Log

长事务的危害 ⭐⭐⭐⭐⭐

如何避免长事务?

  1. 尽快提交或回滚

  2. 拆分大事务

  3. 监控事务运行时间:

3. Binlog(归档日志)

作用

  1. 主从复制:从库通过 Binlog 同步数据

  2. 数据恢复:通过 Binlog 恢复到指定时间点

Binlog vs Redo Log

特性
Redo Log
Binlog

层级

InnoDB 引擎层

MySQL Server 层

内容

物理日志(页的修改)

逻辑日志(SQL 语句)

大小

固定大小,循环写

追加写,不覆盖

作用

崩溃恢复

主从复制、数据恢复

Binlog 格式

  1. STATEMENT

    • 记录 SQL 语句

    • 日志量小

    • 可能导致主从不一致(如 NOW())

  2. ROW

    • 记录每行数据的变化

    • 日志量大

    • 保证主从一致(推荐)

  3. MIXED

    • 混合模式

    • 一般用 STATEMENT,特殊情况用 ROW

参数配置

日志系统总结


事务的最佳实践

1. 保持事务简短

2. 避免长事务

长事务的危害

  • Undo Log 无法清理

  • 锁持有时间长

  • 影响并发性能

  • 可能导致主从延迟

如何避免

3. 合理选择隔离级别

4. 显式开启事务

5. 使用合适的锁粒度

6. 批量操作分批处理


面试高频问题 ⭐⭐⭐⭐⭐

Q1: 事务的 ACID 特性是如何实现的?

回答

  1. Atomicity(原子性)

    • 实现:Undo Log

    • 事务回滚时,使用 Undo Log 恢复数据

  2. Consistency(一致性)

    • 实现:由 A + I + D 共同保证

    • 数据库约束 + 业务规则

  3. Isolation(隔离性)

    • 实现:锁 + MVCC

    • 锁机制:解决写-写冲突

    • MVCC:解决读-写冲突

  4. Durability(持久性)

    • 实现:Redo Log + WAL

    • 先写日志,再写磁盘

    • 崩溃后通过 Redo Log 恢复

Q2: MySQL 有哪几种隔离级别?

回答

四种隔离级别,从低到高:

  1. READ UNCOMMITTED(读未提交)

    • 会出现:脏读、不可重复读、幻读

    • 几乎不使用

  2. READ COMMITTED(读已提交)

    • 会出现:不可重复读、幻读

    • Oracle、SQL Server 默认

    • 适合大多数场景

  3. REPEATABLE READ(可重复读)

    • MySQL 默认

    • 通过 Next-Key Lock 解决了幻读

    • 适合需要可重复读的场景

  4. SERIALIZABLE(串行化)

    • 完全串行执行

    • 性能最差

    • 很少使用

Q3: 什么是脏读、不可重复读、幻读?

脏读

  • 读到其他事务未提交的数据

  • 如果那个事务回滚,读到的是无效数据

不可重复读

  • 同一事务内,多次读取同一数据,结果不一致

  • 原因:其他事务 UPDATE 并提交

幻读

  • 同一事务内,多次查询,结果集不一致

  • 原因:其他事务 INSERT/DELETE 并提交

区别

  • 不可重复读关注单条记录的值

  • 幻读关注结果集的行数

Q4: MySQL 如何解决幻读?

回答

MySQL 的 RR 隔离级别通过两种机制解决幻读:

  1. 快照读(普通 SELECT)

    • 使用 MVCC

    • 读取的是事务开始时的快照

    • 看不到其他事务的插入

  2. 当前读(FOR UPDATE)

    • 使用 Next-Key Lock(临键锁)

    • 锁住索引记录 + 间隙

    • 阻止其他事务插入

示例

Q5: 快照读和当前读的区别?

快照读

  • 普通的 SELECT

  • 读取历史版本(快照)

  • 不加锁

  • 使用 MVCC

  • 并发性能高

当前读

  • SELECT ... FOR UPDATE

  • INSERT、UPDATE、DELETE

  • 读取最新版本

  • 加锁

  • 使用锁机制

  • 并发性能低

Q6: Redo Log 和 Undo Log 的区别?

特性
Redo Log
Undo Log

作用

保证持久性

保证原子性 + MVCC

内容

数据页的新值

数据的旧值

时机

事务提交时写入

修改数据时写入

用途

崩溃恢复(重做)

事务回滚(撤销)

Q7: 什么是两阶段提交?为什么需要?

什么是

事务提交分两个阶段:

  1. Prepare 阶段:写 Redo Log(状态 prepare)

  2. Commit 阶段:写 Binlog,然后写 Redo Log(状态 commit)

为什么需要

保证 Redo LogBinlog 的一致性。

  • Redo Log 用于崩溃恢复(主库)

  • Binlog 用于主从复制(从库)

  • 必须保证两者一致,否则主从数据不一致

示例

Q8: 如何避免长事务?

长事务的危害

  • Undo Log 无法清理

  • 锁持有时间长

  • 影响并发

  • 主从延迟

避免方法

  1. 拆分大事务

  2. 设置超时

  3. 监控长事务

  4. 及时提交或回滚

  5. 不要在事务中执行耗时操作(网络请求、文件 IO)


总结

核心要点 ⭐⭐⭐⭐⭐

  1. ACID 特性

    • Atomicity:Undo Log

    • Isolation:锁 + MVCC

    • Durability:Redo Log + WAL

    • Consistency:A + I + D

  2. 隔离级别

    • RU:脏读 + 不可重复读 + 幻读

    • RC:不可重复读 + 幻读

    • RR:解决了幻读(MySQL 默认)

    • SERIALIZABLE:完全隔离

  3. 并发问题

    • 脏读:读未提交

    • 不可重复读:UPDATE 并提交

    • 幻读:INSERT/DELETE 并提交

  4. 幻读的解决

    • 快照读:MVCC

    • 当前读:Next-Key Lock

  5. 日志系统

    • Redo Log:持久性

    • Undo Log:原子性 + MVCC

    • Binlog:主从复制

记住这些关键点

  • ACID 的实现机制

  • 四种隔离级别及其问题

  • 脏读、不可重复读、幻读的区别

  • 快照读 vs 当前读

  • Redo Log vs Undo Log

  • 两阶段提交

  • 避免长事务


下一步:学习 MVCC 原理,深入理解多版本并发控制。

Last updated