📄 一句话概括
回滚(ROLLBACK)是事务的后悔药:BEGIN 之后的操作全部作废,数据库回到 BEGIN 前一刻的原样——已经执行的 UPDATE/DELETE 也照样撤得干干净净,像从没发生过。
📝 本节和前面几节的关系
COMMIT(4-15)是「全对,转正」,ROLLBACK 是「出事,作废」——同一枚硬币的两面。本节讲清:为什么能撤(草稿区机制)、什么时机撤(程序里先检查后提交)、以及撤不干净的例外(COMMIT 过了、DDL 语句、自增号)。
一、为什么能撤:草稿区
BEGIN 之后,改动都写在「草稿区」(日志+缓存),原表一字未动。ROLLBACK 的动作就是把草稿一扔——原样自然还在。所以:
- 回滚不是「反向再执行一遍 UPDATE」
- 是从未让改动碰到正式数据——撤起来天然干净
二、两种触发方式
1. 手动:代码里先检查后决定
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE name = '甲';
UPDATE accounts SET balance = balance + 100 WHERE name = '乙';
-- 检查:乙的账户被冻结了!
ROLLBACK; -- 两步全作废
2. 自动:出错时数据库自己撤
BEGIN;
UPDATE t SET a = 1 WHERE id = 1;
UPDATE t SET b = '类型全错的值' WHERE id = 2; -- 这条报错
-- 事务进入失败态,后续会被要求 ROLLBACK
程序设计惯例:出错必 ROLLBACK,没错才 COMMIT——只有这两条出口,没有第三条。
三、回滚撤不掉的三样东西
| 撤不掉 | 原因 |
|---|---|
| 已 COMMIT 的 | 已经转正,草稿区早清空了 |
| DDL 语句(部分数据库) | DROP TABLE 等建删结构的操作多半隐式提交,事务管不着 |
| 自增编号空洞 | 号已发出(4-11 伏笔回收),回滚不回收 |
四、动手 Demo:撤得干净 vs 撤不回
CREATE TABLE t (id INT, n INT);
INSERT INTO t VALUES (1, 100);
-- 撤得干净
BEGIN;
UPDATE t SET n = 0 WHERE id = 1;
ROLLBACK;
SELECT * FROM t; -- n 还是 100 ✅
-- 撤不回:COMMIT 过了
BEGIN;
UPDATE t SET n = 50 WHERE id = 1;
COMMIT;
ROLLBACK; -- 晚了!COMMIT 后新事务里没有东西可撤
SELECT * FROM t; -- n = 50
第二个场景里 ROLLBACK 不报错,但撤的是个空事务——很多事故正是「以为还没提交,其实已提交」。
五、30 秒实验
BEGIN;
DELETE FROM t; -- 全删了!
SELECT * FROM t; -- 空,吓一跳
ROLLBACK; -- 冷静
SELECT * FROM t; -- 完璧归赵
亲手体验「误删全表也能救回」——这就是事务给的底气(前提:没 COMMIT)。
六、常见误区
- 「ROLLBACK 能撤已提交的数据」:不能;要撤已提交的,得用备份/日志恢复那套重炮
- 「写了 ROLLBACK 就万事大吉」:位置要对——出错分支里;写不到的分支(异常没接住)等于没写
自测三问
1. 为什么回滚能撤得干净?(改动只在草稿区,原表未动) 2. 三样撤不掉的是什么?(已提交的/DDL/自增号空洞) 3. 程序里事务的出口有哪两条?(出错 ROLLBACK,没错 COMMIT)- ☐ 下一节(4-17 死锁-两个处理发生冲突导致处理停止的问题)
相关笔记
- COMMIT-执行一组处理 —— 上一节:镜像正面
- 死锁-两个处理发生冲突导致处理停止的问题 —— 下一节:事务的代价
- AUTO_INCREMENT-自动输入连续的编号 —— 编号空洞伏笔
- 🗄️ 数据库基础 · 索引 —— 全课路线图