事务是「一组语句要么全成、要么全不成」的保证。 它是数据安全的最后一层,
也是前面所有写操作的兜底。
Demo 里删了一行,ROLLBACK 之后从四行回到五行;
换成 COMMIT 就再也撤不回来(再 ROLLBACK 报 no transaction is active);
SAVEPOINT 能只撤一半:三个数是 3、2、3。
第 19 到 21 章一直在写数据, 但每一句都是「立刻生效、无法反悔」。 这一章给写操作加上「反悔键」。 事务是 ACID 里最实用的那个 A,也是后面做并发、做恢复的地基。
一、事务:要么全成,要么全不成
转账是标准例子:A 扣 100、B 加 100,两条必须一起成功。 如果只成了一条,
钱就凭空消失或凭空多出来。
事务就是把几条语句捆成一个单位,引擎保证一起生效或一起不生效。
中途断电、出错,全都当作没发生过。
三个动词:BEGIN 开启(或 START TRANSACTION)、COMMIT 提交、ROLLBACK 回滚。
开了不提交,改动就悬着。
二、ROLLBACK 与 COMMIT
ROLLBACK 撤销事务内的所有改动,数据回到 BEGIN 那一刻。Demo 里删掉的那行原样回来了。 注意它撤销的是「改动」,不是「时间」——别把它当成时光机,它只是把这次事务写的东西抹掉。 COMMIT 是确认。 确认之后就不能再用 ROLLBACK 撤, 只能靠备份恢复。 Demo 里再 ROLLBACK 时报 no transaction is active,意思就是「没有进行中的事务可撤」。 实务上有个纪律:改完先 SELECT 看一眼,确认没问题再 COMMIT。 这比「出事了再想恢复」便宜一百倍。
三、SAVEPOINT:只撤一部分
SAVEPOINT 名字 在事务里打个标记,ROLLBACK TO 名字 只退到这个标记,之前的改动保留。
RELEASE 名字 是「丢掉这个标记」,不是提交;最后还得靠 COMMIT 定案。
长流程特别需要它:十步里有八步成功、第九步失败,只退第九步重来,不用整批从头跑。
四、ACID 四个字母
A 原子性:要么全成要么全不成,没有中间态。 C 一致性:事务前后数据都满足约束。这一条其实靠 A 与约束共同保证。 I 隔离性:并发事务互相看不见对方的中间状态。 隔离级别就是在这条上做取舍——越严越安全越慢。 D 持久性:提交之后断电也不丢。靠日志先写盘再返回成功来实现。 四个词记住顺序就好:先保证不半途而废(A),再谈并发(I),最后谈落盘(D)。
五、MySQL 侧的两个差异
一是默认自动提交。 MySQL 每条语句默认就是一个事务,
要控制就得 SET autocommit = 0 或显式 BEGIN。
本课这套引擎在 Demo 里把自动提交关了,才能手动 BEGIN/ROLLBACK。
二是 DDL 会隐式提交。 MySQL 里 CREATE TABLE、ALTER TABLE 会悄悄把当前事务提交掉,
之前的改动就撤不回来了。 SQLite 允许 DDL 进事务,这一点两边不一样。
排查经验:明明 ROLLBACK 了却没生效,先看一眼事务里是不是夹了 DDL。
六、小结
教材小结:事务用 BEGIN 开启、COMMIT 提交、ROLLBACK 回滚,保证一组操作全成或全不成; MySQL 默认自动提交且 DDL 会隐式提交。 本课补三条:一是 COMMIT 之后撤不回来;二是 SAVEPOINT 能只撤一部分; 三是改完先 SELECT 再 COMMIT,这条纪律最省钱。
七、动手 Demo:删了五行,再一个个放回来
Demo 六段:先看库里有几行,然后演示 ROLLBACK 全撤、COMMIT 后撤不回、SAVEPOINT 只撤一半, 最后讲 ACID 和 MySQL 的两个差异。 盯住那几个 COUNT:5 → 4 → 5 → 4 → 3 → 2 → 3,每一步都能对上账。
import sqlite3
con = sqlite3.connect(":memory:")
con.isolation_level = None # 关掉自动提交,才能手动 BEGIN
# 改完反悔:BEGIN 之后的改动全撤
con.execute("BEGIN")
con.execute("DELETE FROM customers WHERE cust_id = 10002")
con.execute("SELECT COUNT(*) FROM customers") # 4
con.execute("ROLLBACK")
con.execute("SELECT COUNT(*) FROM customers") # 5
# 确认之后不能再撤
con.execute("BEGIN")
con.execute("DELETE FROM customers WHERE cust_id = 10003")
con.execute("COMMIT")
con.execute("ROLLBACK") # 报错:no transaction is active
# SAVEPOINT:只撤一部分
con.execute("BEGIN")
con.execute("DELETE FROM customers WHERE cust_id = 10004")
con.execute("SAVEPOINT sp1")
con.execute("DELETE FROM customers WHERE cust_id = 10005")
con.execute("ROLLBACK TO sp1") # 只退到保存点
con.execute("RELEASE sp1")
con.execute("COMMIT")
实跑输出:
--------------------------------------------------------------------
第二十六课:管理事务处理
--------------------------------------------------------------------
【一】事务是什么:要么全成,要么全不成
**转账是标准例子**:A 账户扣 100、B 账户加 100,**两条必须一起成功。** 中间出错只成一条,
钱就凭空少了或多了。
**事务就是把几条语句捆成一个单位**,引擎保证它们**一起生效或一起不生效**。
SQL> SELECT COUNT(*) AS n FROM customers
n
--
5
(1 行)
【二】ROLLBACK:改完反悔,全撤
SQL> BEGIN
完成
SQL> DELETE FROM customers WHERE cust_id = 10002
影响 1 行
SQL> SELECT COUNT(*) AS n FROM customers
n
--
4
(1 行)
SQL> ROLLBACK
完成
SQL> SELECT COUNT(*) AS n FROM customers
n
--
5
(1 行)
**删完只剩四行,ROLLBACK 之后又回到五行。** 删掉的那行原样回来了。
**`BEGIN` 开启事务,`ROLLBACK` 撤销事务内的所有改动。** **注意它撤销的是「改动」,不是「时间」**
——回滚之后数据回到 BEGIN 那一刻。
【三】COMMIT:确认了就不能再撤
SQL> BEGIN
完成
SQL> DELETE FROM customers WHERE cust_id = 10003
影响 1 行
SQL> SELECT COUNT(*) AS n FROM customers
n
--
4
(1 行)
SQL> COMMIT
完成
SQL> SELECT COUNT(*) AS n FROM customers
n
--
4
(1 行)
**再 ROLLBACK 报错:cannot rollback - no transaction is active**
SQL> SELECT COUNT(*) AS n FROM customers
n
--
4
(1 行)
**还是四行。** `COMMIT` 就是「确认」,确认之后**只能靠备份恢复,不能靠回滚。**
【四】SAVEPOINT:只撤一部分
SQL> BEGIN
完成
SQL> DELETE FROM customers WHERE cust_id = 10004
影响 1 行
SQL> SELECT COUNT(*) AS n FROM customers
n
--
3
(1 行)
SQL> SAVEPOINT sp1
完成
SQL> DELETE FROM customers WHERE cust_id = 10005
影响 1 行
SQL> SELECT COUNT(*) AS n FROM customers
n
--
2
(1 行)
SQL> ROLLBACK TO sp1
完成
SQL> SELECT COUNT(*) AS n FROM customers
n
--
3
(1 行)
SQL> RELEASE sp1
完成
SQL> COMMIT
完成
**三个数:3、2、3。** 第一次删到剩三行,再删剩两行,
**`ROLLBACK TO sp1` 只撤到保存点,于是回到三行。**
**`RELEASE sp1` 是「丢掉这个保存点」**,不是提交;最后还得靠 COMMIT 定案。
**这一套在长流程里很有用**:中间某步错了只退那一步,不用整批重来。
【五】ACID 四个字母
字母 词 白话
------------------------------------------------------------------------
A 原子性 Atomicity 要么全成要么全不成,没有中间态
C 一致性 Consistency 事务前后数据都满足约束
I 隔离性 Isolation 并发事务互不看见中间状态
D 持久性 Durability 提交之后断电也不丢
**这四个词是理解事务的骨架**:原子性靠日志,持久性靠日志加落盘,
**隔离性靠锁或版本号**——并发要调的那几个旋钮,全都建立在这四个词上。
【六】MySQL 侧的两个差异
**一是默认自动提交。** MySQL 每条语句默认就是一个事务,
**要关掉得写 `SET autocommit = 0`,或者显式 BEGIN。**
**本课这套引擎在 Demo 里把自动提交关了,才能手动 BEGIN/ROLLBACK。**
**二是 DDL 会隐式提交。** MySQL 里 `CREATE TABLE`、
`ALTER TABLE` 这些**会悄悄把当前事务提交掉**,之前的改动就撤不回来了。
**SQLite 允许事务里跑 DDL,这一点两边不一样。**
**实操建议:能上事务就上,尤其是「多条语句改同一件业务」的时候。**
第一段五行。 这是后面所有数字的基准。 第二段 ROLLBACK。 删一行剩四行,回滚后又回到五行。 第三段 COMMIT。 提交后四行,再 ROLLBACK 报「没有进行中的事务」。 第四段 SAVEPOINT。 删两次得 3、2,退回保存点回到 3。 第五段 ACID。 四个字母各一句白话。 第六段 MySQL 差异。 自动提交、DDL 隐式提交——后者是「回滚没生效」的最常见原因。
30 秒实验
- 在 BEGIN 之后插一行新客户,再 ROLLBACK,这行还在吗?
- (先想再跑)COMMIT 之后再执行 BEGIN 然后 ROLLBACK,会报错吗?
- 把第四段的
ROLLBACK TO sp1换成ROLLBACK,COUNT 会变成几?
常见误区
- 以为 COMMIT 之后还能 ROLLBACK。不能,只能靠备份恢复。
- 事务里混进 DDL 还指望回滚。MySQL 会隐式提交,改动撤不回来。
- 开了事务忘了提交。改动悬着,别的连接看不到,还可能长时间占锁。
自测三问(点开对答案)
1. 事务的四个特性是什么? —— 原子性、一致性、隔离性、持久性,合称 ACID。 2. ROLLBACK 和 COMMIT 的区别? —— ROLLBACK 撤销事务内的改动,回到 BEGIN 那一刻;COMMIT 确认生效,之后无法撤销。 3. 为什么在 MySQL 里 ROLLBACK 有时「没生效」? —— 因为事务里执行了 CREATE/ALTER 这类 DDL,它会隐式提交之前的改动。数据安全这块补完了。 接下来三章换个话题:中文、emoji、时区这些「出了国界就出问题」的事——全球化与本地化。
- ☐ (本章完,下一章 第27章 全球化与本地化)