事务与 ACID 特性
本教程共 46 篇 · 第 37 篇 · 更新于 2026-07-30 · 约 13 分钟阅读
37. 事务与 ACID 特性
本节目标:搞清楚事务(Transaction)到底是什么,为什么需要它;深入理解 ACID 四大特性的含义和实现原理;掌握 BEGIN/COMMIT/ROLLBACK/SAVEPOINT 的用法和自动提交机制。
37.1 为什么需要事务
先看一个经典场景:用户 A 给用户 B 转账 100 元。这个操作分两步:
- A 的余额减 100;
- 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用
BEGIN或START TRANSACTION开启事务时,会临时暂停自动提交,直到COMMIT或ROLLBACK后恢复。所以你不需要先关 autocommit 再开事务,直接BEGIN就行。
37.6 隐式提交
有些 SQL 语句会偷偷帮你提交当前事务,这叫隐式提交(Implicit Commit)。即使你在事务中执行了这些语句,MySQL 也会先 COMMIT 当前事务,再执行该语句。
会触发隐式提交的语句包括:
- DDL 语句:
CREATE、ALTER、DROP(建库、建表、改表结构等); CREATE INDEX、DROP INDEX;CREATE USER、GRANT、REVOKE等权限操作;LOCK TABLES、UNLOCK 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;
- 事务要短、要小、出错就回滚。
下一节深入事务隔离级别,看多个事务并发执行时会出什么问题,以及怎么解决。