MySQL

MySQL 事务原理与实现机制详解

·16 分钟阅读·6186 字

整理 MySQL 事务原理与实现机制详解 的核心概念、关键流程与实践要点

📋 目录

MySQL 事务原理与实现机制详解

1. 事务产生的背景

数据库事务的核心目标,是在复杂业务操作中保证数据的一致性。现实中的一个业务动作通常不是由一条 SQL 完成,而是一组具有逻辑关联的数据库操作。如果这些操作无法被作为一个整体管理,就可能出现业务状态不正确的问题。

例如,在电商系统中,用户购买一个商品通常需要执行以下几个操作:

BEGIN;

INSERT INTO orders(order_id, user_id, product_id)
VALUES(10001, 20001, 30001);

UPDATE inventory
SET stock = stock - 1
WHERE product_id = 30001;

UPDATE account
SET balance = balance - 99
WHERE user_id = 20001;

COMMIT;

这三个操作分别修改订单、库存和账户数据。从数据库角度看,它们是三个独立的 SQL 操作,但是从业务角度看,它们属于同一个业务事件。

如果订单写入成功,但是库存扣减失败,系统会出现“超卖”风险;如果账户扣款成功,但是订单创建失败,用户会遇到资金减少但订单不存在的问题。

因此数据库需要提供事务机制,使多个操作具有整体性。事务要求一组操作必须满足:

  • 所有操作全部成功,事务才提交;

  • 任意操作失败,之前完成的修改都可以撤销;

  • 多个事务并发执行时,不会产生错误的数据状态;

  • 数据库发生故障后,已经提交的数据不会丢失。


2. MySQL事务的实现基础

MySQL本身包含多个层次,其中事务主要由存储引擎实现。

整体结构如下:

客户端

↓

MySQL Server层

↓

存储引擎层

↓

InnoDB

MySQL Server层主要负责 SQL 解析、优化以及执行计划生成,而事务相关的数据一致性能力主要由 InnoDB 提供。

InnoDB 实现事务主要依赖以下几个核心机制:

  • Undo Log 用于保存数据修改前的状态,实现事务回滚,同时为 MVCC 提供历史版本;

  • Redo Log 用于保存数据修改过程,实现数据库崩溃后的恢复;

  • Lock 用于控制多个事务之间的数据访问冲突;

  • MVCC 用于提高并发读取能力。

因此,理解 MySQL 事务的关键,不是简单理解 BEGIN、COMMIT 和 ROLLBACK,而是理解这些机制如何共同保证事务的四个核心特性。


3. 事务的 ACID 特性

4. 原子性(Atomicity)

原子性表示一个事务中的所有操作必须作为一个整体执行,不能出现部分成功的情况。

例如银行转账:

BEGIN;

UPDATE account
SET balance = balance - 100
WHERE id = 1;

UPDATE account
SET balance = balance + 100
WHERE id = 2;

COMMIT;

如果第二条 SQL 执行失败,那么第一条 SQL 对账户余额的修改也必须撤销。

InnoDB 通过 Undo Log 保存修改之前的数据,因此可以在事务失败时恢复原始状态。


5. 持久性(Durability)

持久性表示事务一旦提交成功,即使数据库发生崩溃,已经提交的数据也不能丢失。

例如:

COMMIT;

执行成功后,客户端认为数据已经保存。

如果此时服务器突然断电,数据库重新启动后仍然需要恢复该事务。

InnoDB 通过 Redo Log 实现这一能力。


6. 隔离性(Isolation)

隔离性解决多个事务同时执行时的数据访问问题。

例如:

事务 A:

UPDATE account
SET balance=900
WHERE id=1;

事务 B:

SELECT balance
FROM account
WHERE id=1;

事务 B 是否可以看到事务 A 修改后的数据,需要由事务隔离机制决定。

InnoDB 通过锁和 MVCC 实现隔离。


7. 一致性(Consistency)

一致性是事务最终希望达到的目标。

它不是由某一个组件单独完成,而是由:

  • 原子性;

  • 隔离性;

  • 持久性;

  • 数据约束;

共同保证。

例如账户余额不能小于0,这种业务约束需要数据库约束和事务机制共同保证。


8. InnoDB事务执行过程

为了理解事务内部机制,假设有如下表:

CREATE TABLE account(
    id INT PRIMARY KEY,
    balance INT
);

当前数据:

id     balance

1      1000

执行:

BEGIN;

UPDATE account
SET balance=900
WHERE id=1;

COMMIT;

下面分析 InnoDB 内部发生的过程。


9. 创建事务

执行:

BEGIN;

之后,InnoDB 创建事务对象,并分配事务 ID。

例如:

Transaction ID = 100

事务对象会记录当前事务的状态,包括:

  • 事务生命周期;

  • 修改的数据;

  • 持有的锁;

  • MVCC相关信息。


10. 修改数据页

执行 UPDATE 时,InnoDB 并不会直接修改磁盘中的数据文件。

实际流程是:

磁盘数据页

↓

Buffer Pool

↓

修改内存中的数据页

Buffer Pool 是 InnoDB 的缓存区域。

这样设计的原因是磁盘随机 IO 成本较高。如果每一次 UPDATE 都直接修改磁盘,那么大量小事务会产生大量随机写操作,数据库性能会明显下降。

因此 InnoDB 采用:

先修改内存中的数据页,再通过日志机制保证数据可靠。


11. Undo Log 的作用

Undo Log 保存数据修改之前的信息。

例如:

修改前:

balance = 1000

修改后:

balance = 900

Undo Log 会记录:

事务ID:

100

数据:

balance=1000

如果事务执行失败:

ROLLBACK;

InnoDB 可以根据 Undo Log:

900

恢复

1000

因此 Undo Log 实现了事务回滚。

但是 Undo Log 不只是用于回滚,它还是 MVCC 的基础。

原因在于:

在高并发环境下,一个事务修改数据之后,其他事务可能仍然需要读取修改之前的数据版本。

如果没有旧版本保存,所有读取操作都必须等待写事务结束,数据库并发能力会大幅下降。


12. Redo Log 的作用

Undo Log 解决的是:

事务失败后如何恢复。

但是数据库还存在另一个问题:

事务提交成功以后,如果服务器突然崩溃,如何保证数据不丢失。

例如:

事务执行:

balance:

1000 → 900

然后:

COMMIT;

返回成功。

但是此时可能出现:

Buffer Pool:

balance=900


磁盘文件:

balance=1000

如果服务器立即断电,内存中的数据会丢失。

因此 InnoDB 引入 Redo Log。

Redo Log 保存的是:

已经发生的数据修改。

例如:

balance:

1000 → 900

数据库恢复时,可以重新执行这些修改。


13. WAL机制和事务提交过程

InnoDB 使用 WAL(Write Ahead Logging)机制。

它的核心思想是:

数据文件修改之前,必须保证对应日志已经安全保存。

事务提交过程大致如下:

执行SQL

↓

修改Buffer Pool

↓

生成Redo Log

↓

Redo Log刷入磁盘

↓

事务提交成功

↓

后台刷新数据文件

这里有一个重要设计:

事务提交成功时,并不要求数据文件已经立即更新。

只要 Redo Log 已经持久化,数据库就可以保证故障恢复。


14. MySQL崩溃恢复

假设:

事务执行:

balance:

1000 → 900

已经提交。

但是数据文件还没有刷新。

此时:

服务器断电

磁盘中的数据:

balance=1000

Redo Log:

balance:

1000→900

MySQL重新启动后,InnoDB 会进入恢复阶段。

恢复过程:

  1. 检查 Redo Log;

  2. 找到已经提交但没有写入数据文件的事务;

  3. 重新执行修改;

  4. 恢复数据一致性。

最终:

balance=900

15. Undo Log 与 Redo Log 的区别

两者虽然都是日志,但是解决的问题不同。

Undo Log 保存的是修改之前的数据状态,用于撤销事务。

例如:

900

↓

rollback

↓

1000

Redo Log 保存的是修改之后的操作过程,用于数据库恢复。

例如:

1000

↓

crash recovery

↓

900

简单来说:

Undo Log 解决“如何回到过去”。

Redo Log 解决“如何恢复已经提交的未来状态”。


16. MVCC实现并发读取

如果数据库完全依赖锁控制并发:

事务 A:

UPDATE account
SET balance=900
WHERE id=1;

事务 B:

SELECT balance
FROM account
WHERE id=1;

事务 B 可能需要等待事务 A 完成。

在高并发系统中,大量读请求会被写操作阻塞。

因此 InnoDB 使用 MVCC:

Multi-Version Concurrency Control。

它的核心思想是:

数据修改时保留多个版本,让不同事务根据自己的时间视图读取对应版本。

例如:

原数据:

balance=1000

修改后:

balance=900

数据库内部保存:

旧版本:

1000


新版本:

900

不同事务根据 Read View 判断应该读取哪个版本。


17. 事务隔离级别

18. Read Uncommitted

允许读取未提交数据。

例如:

事务 A:

修改:

balance=900

但没有提交。

事务 B:

读取:

900

随后事务 A 回滚:

balance=1000

事务 B 读取到了不存在的数据,这称为脏读。


19. Read Committed

只能读取已经提交的数据。

它解决了脏读。

但是同一个事务中,两次读取可能得到不同结果。

例如:

第一次查询:

balance=1000

另一个事务提交:

balance=900

第二次查询:

balance=900

这种情况称为不可重复读。


20. Repeatable Read

MySQL InnoDB默认隔离级别。

它通过 MVCC 保证:

同一个事务中,多次读取结果一致。


21. Serializable

最高隔离级别。

事务按照串行方式执行。

一致性最好,但是并发能力最低。


22. 主从复制环境中的事务

生产环境通常采用主从架构:

        写请求

           ↓

        Master

           ↓

   ----------------

   Slave1     Slave2

事务不仅需要在主库正确执行,还需要同步到从库。

主库执行事务时:

  1. 修改 InnoDB 数据;

  2. 写 Redo Log;

  3. 写 Binlog;

  4. 提交事务。

其中:

Redo Log 用于本地恢复。

Binlog 用于主从复制。


从库复制流程:

Master

↓

Binlog

↓

IO Thread

↓

Relay Log

↓

SQL Thread

↓

执行事务

23. 主从环境中的事务一致性问题

主从复制采用异步复制时,主库提交成功并不代表从库已经完成。

例如:

10:00:00

Master:

COMMIT成功


10:00:01

Slave:

仍未执行

如果业务立即读取 Slave,可能读取不到刚刚提交的数据。

这就是主从延迟。

常见解决方式包括:

第一,写后读主。对于刚写入的数据,强制从主库读取。

第二,半同步复制。主库提交事务后,需要等待至少一个从库确认,然后才返回成功。


24. 分片环境中的事务

当单机数据库无法满足容量和性能需求时,通常会采用分库分表。

例如:

订单库:

order_db_0


库存库:

inventory_db_1


支付库:

payment_db_2

创建订单时,需要同时修改多个数据库。

但是 MySQL 本地事务只能管理一个数据库实例。

因此:

一个业务事务

↓

多个MySQL实例

↓

分布式事务

产生。


25. 分布式事务解决方案

26. XA两阶段提交

XA通过事务协调者管理多个数据库。

第一阶段:

协调者要求所有数据库准备提交。

第二阶段:

所有数据库统一提交。

优点是可以实现强一致性。

缺点是性能较差,因为事务准备阶段需要长期占用资源,如果协调者故障,还可能导致参与者等待。


27. 最终一致性方案

互联网系统更常使用最终一致性。

例如订单系统:

订单服务:

创建订单

提交本地事务

然后发送消息:

消息队列

库存服务消费消息:

扣减库存

每个服务保证自己的本地事务,通过消息最终达到一致。


28. 生产环境中的事务问题

在实际生产环境中,事务相关问题主要集中在性能、一致性和故障恢复三个方面。

长事务是比较常见的问题。由于 MVCC 需要保留历史版本,如果某个事务持续时间过长,旧版本数据无法及时清理,会导致 Undo Log 增长,并增加数据库存储压力。

锁竞争也是重要问题。当多个事务同时修改热点数据时,会产生锁等待。如果事务之间形成循环等待,则会产生死锁。

在主从架构中,大事务可能产生大量 Binlog,导致从库执行速度跟不上主库,从而出现复制延迟。

在分片架构中,跨分片事务会增加系统复杂度,需要在强一致性和性能之间进行权衡。


29. 从云数据库运维角度理解事务

对于云数据库平台来说,事务治理不仅是监控事务数量,而是需要分析事务行为对系统整体性能的影响。

通常需要关注:

  • TPS变化;

  • Commit和Rollback比例;

  • 长事务数量;

  • 锁等待时间;

  • 死锁数量;

  • Undo空间增长;

  • Redo Log写入压力;

  • Binlog增长速度;

  • 主从复制延迟。

例如某个数据库实例出现:

  • CPU升高;

  • IO增加;

  • 查询延迟升高;

  • Undo空间持续增长。

不能简单认为是资源不足,还需要分析是否存在长事务、热点更新、大量锁竞争等事务层问题。

这也是数据库自动化运维和 AIOps 需要解决的问题:通过多个指标之间的关联关系,自动判断数据库当前真正的问题来源。


30. 总结

MySQL事务并不是简单的 SQL 控制语法,而是一套由日志、锁、版本控制和复制机制共同组成的数据一致性系统。

在单机 InnoDB 中,Undo Log负责事务回滚和历史版本保存,Redo Log负责崩溃恢复,MVCC和锁机制负责并发控制。

在主从架构中,事务通过 Binlog 传播到其他节点,需要解决复制延迟和故障一致性问题。

在分片架构中,本地事务无法跨越多个数据库,需要通过 XA 或最终一致性方案解决分布式事务问题。

理解 MySQL 事务的关键,不是记住 ACID 四个字,而是理解 InnoDB 如何通过日志、版本控制和一致性机制,在不同规模的数据库架构中保证数据正确性。


关联文档

Yanche Blog

记录云原生、Linux、数据库等技术领域的学习心得,以及日常生活的思考与感悟。

© 2026 Yanche Blog. All rights reserved.

Powered by Astro