首页 / MySQL 入门教程 / 事务与 ACID 特性

MySQL 入门教程

事务与 ACID 特性

本教程共 46 篇 · 第 37 篇 · 更新于 2026-07-30 · 约 13 分钟阅读

MySQLMySQL 入门教程事务ACIDCOMMITROLLBACKSAVEPOINTInnoDB

37. 事务与 ACID 特性

本节目标:搞清楚事务(Transaction)到底是什么,为什么需要它;深入理解 ACID 四大特性的含义和实现原理;掌握 BEGIN/COMMIT/ROLLBACK/SAVEPOINT 的用法和自动提交机制。

37.1 为什么需要事务

先看一个经典场景:用户 A 给用户 B 转账 100 元。这个操作分两步:

  1. A 的余额减 100;
  2. B 的余额加 100。

如果第一步成功、第二步失败(比如数据库崩溃),A 的钱少了 100,B 没收到,钱凭空消失了。这种数据不一致是致命的。

事务(Transaction) 就是为解决这个问题而生的:它把一组操作打包成一个不可分割的执行单元,要么全部成功,要么全部撤销。

-- 转账事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;  -- A 减钱
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;  -- B 加钱
COMMIT;  -- 两步都成功,提交

如果中间出错了:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
-- 假设这里出错了
ROLLBACK;  -- 回滚,A 减的 100 恢复
Note

事务是 InnoDB 存储引擎的核心特性。MyISAM 不支持事务,这也是实际项目几乎都用 InnoDB 的原因之一。

37.2 事务的基本用法

开启事务

-- 方式一(推荐,标准 SQL)
BEGIN;

-- 方式二(等价)
START TRANSACTION;

-- 方式三(MySQL 8.0+ 支持只读事务声明)
START TRANSACTION READ ONLY;

提交事务

COMMIT;

COMMIT 表示事务中的所有操作确认生效,永久写入数据库。

回滚事务

ROLLBACK;

ROLLBACK 表示撤销事务中所有操作,数据恢复到事务开始前的状态。

完整示例

-- 开启事务
BEGIN;

-- 插入用户
INSERT INTO users(username, email, age) VALUES('zhangsan', 'zhangsan@test.com', 25);

-- 查看当前会话能看到这条数据(事务内可见)
SELECT * FROM users WHERE username = 'zhangsan';

-- 决定回滚
ROLLBACK;

-- 再查,数据没了
SELECT * FROM users WHERE username = 'zhangsan';  -- 空结果

37.3 SAVEPOINT:部分回滚

有时候事务很长,不想一出错就全部回滚。SAVEPOINT(保存点)可以标记一个位置,之后可以选择只回滚到这个点,而不是回到事务开头。

BEGIN;

INSERT INTO users(username, email) VALUES('user1', 'user1@test.com');
SAVEPOINT sp1;  -- 标记保存点

INSERT INTO users(username, email) VALUES('user2', 'user2@test.com');
SAVEPOINT sp2;  -- 再标记一个

INSERT INTO users(username, email) VALUES('user3', 'user3@test.com');

-- 发现第三条有问题,回滚到 sp2
ROLLBACK TO SAVEPOINT sp2;
-- 此时 user3 被撤销,user1 和 user2 还在

-- 继续做其他操作
INSERT INTO users(username, email) VALUES('user4', 'user4@test.com');

COMMIT;  -- 最终提交:user1, user2, user4 生效,user3 没有
-- 删除保存点(不需要了就释放)
RELEASE SAVEPOINT sp1;
Tip

保存点在复杂事务中很有用。比如一个事务里做了 10 步操作,第 7 步出错,可以回滚到第 6 步的保存点,重试第 7 步,而不是从头来过。

37.4 ACID 四大特性

ACID 是事务的四大特性,每个字母代表一个核心保证。

A - 原子性(Atomicity)

原子性:事务中的所有操作要么全部成功,要么全部撤销,不存在”做了一半”的状态。

实现原理:InnoDB 用 undo log(回滚日志) 实现。每条修改操作执行前,先在 undo log 中记录”怎么撤销这个操作”。如果事务需要回滚,就根据 undo log 反向执行撤销。

UPDATE 操作 -> 先写 undo log(记录旧值)-> 再修改数据
回滚时 -> 读 undo log -> 把数据恢复成旧值

C - 一致性(Consistency)

一致性:事务执行前后,数据库必须从一个合法状态变成另一个合法状态。

“合法状态”指的是满足所有约束(主键、外键、CHECK、触发器等)。比如转账前后,两人的总金额不变;插入数据不会违反唯一约束。

Note

一致性是事务的最终目标,原子性、隔离性、持久性都是为保证一致性服务的。应用层也需要配合:比如转账前检查余额是否充足,这个校验逻辑是应用层的责任,不是数据库能自动做的。

I - 隔离性(Isolation)

隔离性:多个并发事务同时执行时,一个事务的中间状态不应该被其他事务看到。

完全隔离的代价很高(性能差),所以 MySQL 提供了四种隔离级别,在隔离性和性能之间做取舍。这部分是下一节的重点,这里先知道有这么个事。

D - 持久性(Durability)

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

实现原理:InnoDB 用 redo log(重做日志) 实现。修改数据时,先写 redo log(顺序写,很快),再写磁盘上的数据页。如果崩溃了,重启后根据 redo log 恢复已提交的数据。

COMMIT -> 先把 redo log 刷到磁盘(fsync)-> 返回成功给客户端
崩溃恢复 -> 读 redo log -> 把已提交但没来得及写盘的数据重做一遍

ACID 总结

特性含义InnoDB 实现机制
原子性全做或全不做undo log(回滚日志)
一致性数据始终合法约束 + 原子性 + 隔离性 + 持久性
隔离性并发互不干扰锁 + MVCC(多版本并发控制)
持久性提交后不丢redo log(重做日志)

37.5 自动提交(AUTOCOMMIT)

MySQL 默认开启自动提交(AUTOCOMMIT) 模式:每条 SQL 语句执行完就自动 COMMIT,相当于每条语句都是一个独立的事务。

-- 查看自动提交状态
SELECT @@autocommit;
-- 1 表示开启(默认)

-- 关闭自动提交
SET autocommit = 0;

-- 关闭后,每条语句不会自动提交,需要手动 COMMIT
INSERT INTO users(username, email) VALUES('test', 'test@test.com');
-- 此时其他会话看不到这条数据(还没提交)
COMMIT;  -- 手动提交后其他会话才能看到
autocommit行为
1(默认)每条 SQL 自动提交
0需要手动 COMMIT/ROLLBACK
Tip

BEGINSTART TRANSACTION 开启事务时,会临时暂停自动提交,直到 COMMITROLLBACK 后恢复。所以你不需要先关 autocommit 再开事务,直接 BEGIN 就行。

37.6 隐式提交

有些 SQL 语句会偷偷帮你提交当前事务,这叫隐式提交(Implicit Commit)。即使你在事务中执行了这些语句,MySQL 也会先 COMMIT 当前事务,再执行该语句。

会触发隐式提交的语句包括:

  • DDL 语句:CREATEALTERDROP(建库、建表、改表结构等);
  • CREATE INDEXDROP INDEX
  • CREATE USERGRANTREVOKE 等权限操作;
  • LOCK TABLESUNLOCK TABLES
  • LOAD DATA
  • TRUNCATE TABLE(虽然是清空数据,但本质是 DDL,会隐式提交)。
BEGIN;
INSERT INTO users(username) VALUES('test');
ALTER TABLE users ADD COLUMN phone VARCHAR(20);  -- 这里隐式提交了上面的 INSERT
ROLLBACK;  -- 回滚无效,INSERT 已经被隐式提交了
Warning

这是个很容易踩的坑。在事务中执行 DDL 语句,前面的 DML 操作会被悄悄提交,后续的 ROLLBACK 就无效了。事务中尽量只做 DML(INSERT/UPDATE/DELETE),DDL 操作放在事务外面做。

37.7 事务的使用原则

1. 事务要短

事务持锁时间长会影响并发性能。一个事务内不要做耗时操作(如网络请求、文件 IO),只做数据库操作。

2. 只包含必要的操作

别把无关的操作塞进同一个事务。查询操作通常不需要放在事务里(除非要求一致性读)。

3. 及时处理异常

事务中某条语句出错后,后续语句可能仍然执行(取决于错误类型)。最佳实践是出错就 ROLLBACK:

-- 应用代码中的伪逻辑
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
-- 如果上面报错了,立刻 ROLLBACK
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
COMMIT;

4. 避免大事务

修改大量数据的事务会产生巨量的 undo log,占用大量回滚段空间,还可能导致锁等待。大范围更新应该分批进行。

Tip

我之前排查过一个线上问题:一个事务里更新了 50 万条数据,跑了 10 分钟,期间相关表几乎被锁死。后来改成每 1000 条一个事务分批提交,总时间差不多,但对其他业务的影响小了很多。

37.8 小结

  • 事务保证一组操作要么全成功、要么全撤销;
  • BEGIN 开启事务,COMMIT 提交,ROLLBACK 回滚;
  • SAVEPOINT 支持部分回滚,不用从头来过;
  • ACID 四特性:原子性(undo log)、一致性(约束保障)、隔离性(锁+MVCC)、持久性(redo log);
  • MySQL 默认自动提交,BEGIN 临时暂停自动提交;
  • DDL 语句会触发隐式提交,事务中避免混入 DDL;
  • 事务要短、要小、出错就回滚。

下一节深入事务隔离级别,看多个事务并发执行时会出什么问题,以及怎么解决。