🐬 第26章 管理事务处理
📄 一句话概括

事务是「一组语句要么全成、要么全不成」的保证。 它是数据安全的最后一层, 也是前面所有写操作的兜底。 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 秒实验

  1. 在 BEGIN 之后插一行新客户,再 ROLLBACK,这行还在吗?
  2. (先想再跑)COMMIT 之后再执行 BEGIN 然后 ROLLBACK,会报错吗?
  3. 把第四段的 ROLLBACK TO sp1 换成 ROLLBACK,COUNT 会变成几?

常见误区

  1. 以为 COMMIT 之后还能 ROLLBACK。不能,只能靠备份恢复。
  2. 事务里混进 DDL 还指望回滚。MySQL 会隐式提交,改动撤不回来。
  3. 开了事务忘了提交。改动悬着,别的连接看不到,还可能长时间占锁。
自测三问(点开对答案) 1. 事务的四个特性是什么? —— 原子性、一致性、隔离性、持久性,合称 ACID。 2. ROLLBACK 和 COMMIT 的区别? —— ROLLBACK 撤销事务内的改动,回到 BEGIN 那一刻;COMMIT 确认生效,之后无法撤销。 3. 为什么在 MySQL 里 ROLLBACK 有时「没生效」? —— 因为事务里执行了 CREATE/ALTER 这类 DDL,它会隐式提交之前的改动。

数据安全这块补完了。 接下来三章换个话题:中文、emoji、时区这些「出了国界就出问题」的事——全球化与本地化。

相关笔记