🦏 第17章 数据备份
📄 一句话概括

事务救不了「改错了」:commit 之后就回不去了。 备份是唯一能在删错之后救你的东西。 逻辑备份导成 SQL(能看懂、能跨版本、慢);物理备份拷文件(快、绑版本)。 常规做法是「物理做日常、逻辑做归档」。 备份的三条铁律:要能恢复而且演练过、要放在另一台机器、要定期且留多份。

📝 本章和前面几章的关系

第 15 章讲事务, 这一章讲它的「补集」—— 事务防「改一半」,备份防「改错了」。 这两件事的分工,很多人在出事之前没分清。 这一章没有复杂语法,全是「怎么做才不出事」的判断。 它比第 12 章的索引更不容易写错,但更容易被忽略。

一、先说清楚:事务救不了你

Demo 里一句 delete 删掉三个学生、然后 commit, 再 rollback 已经没用了——查出来还是两行。 第 15 章说过:commit 之后就回不去了。 日志是用来「崩溃恢复」的,不是用来「人反悔」的。 所以「删错了怎么办」这个问题,两个答案要分清:事务的答案是「没法办」(没提交才有救); 备份的答案是「把昨天那份拿回来」。 这就是为什么「有事务了还要备份」——它们防的是两种完全不同的错。

二、逻辑备份和物理备份

逻辑备份:把数据导成「一串 SQL 语句」,MySQL 的 mysqldump 就是干这个的。 物理备份:直接把数据文件拷走,不管里面是什么格式。 本沙箱的「备份」就是拷 .db 文件。 逻辑备份的好处是「能看懂、能改、能跨版本、能只挑一部分」—— 导出来是个文本文件,grep 一下就知道有没有你要的数据; 坏处是「慢」,大库导一次要几小时,还原更慢(要重新执行几百万条 insert)。 物理备份的好处是「快」(拷文件而已), 坏处是「绑版本、绑平台」—— MySQL 5.7 的数据文件不能直接给 8.0 用,Windows 上的也不能直接给 Linux 用。 所以常规做法是「物理做日常、逻辑做归档」: 每天拷文件(快、能快速恢复),每个月导一次 SQL(留个能跨版本的底)。 两种都做,成本很低,收益互补。

三、全量备份和增量备份

全量:每次备份整份数据。 简单、恢复快(只要一份),但占空间、耗时间。 增量: 只备份「上次备份之后变化的部分」。 省空间、快,但恢复麻烦—— 要先恢复全量,再按顺序叠每个增量,中间缺一个就恢复不出来。 MySQL 的增量靠 binlog(二进制日志):它记着每一条改动数据的语句, 所以「恢复到今天下午三点」这种需求,靠的是「全量 + 重放 binlog 到那个时刻」。 这是误删数据最常见的救命手段。 本沙箱没有 binlog, 只有「拷文件」和「导 SQL」两招。 所以这里的「增量」只能自己想办法: 每天导一份、按日期存着,要回哪一天就用哪一份。 粗糙,但够用。

四、备份的三条铁律

第一条:备份要能恢复,而且你演练过。 「备份文件存在」不等于「能恢复」—— 文件可能是 0 字节的、可能是半截的、可能权限不对读不了。 所以定期要真恢复一次到测试库,看数据对不对。 没演练过的备份,等于没有备份。 第二条:备份要在另一台机器上。 和原库放同一块硬盘,硬盘坏了两个一起没; 放同一台机器,机器被勒索病毒加密了,备份一起被加密。 这一条每年都有人用血泪重新发现一次。 第三条:备份要定期、要留多份。 每天一份、留三十天, 这样「上周三误删了、今天才发现」还有救。 只留最新一份的话,错误数据会把好数据覆盖掉—— 等你发现时,好数据已经不在任何一份备份里了。

五、动手 Demo:先看「事务救不了你」这件事是真的

Demo 就一段:插五个学生、删掉一半、commit、再 rollback,看数据能不能回来。 这段的意义是「破一个幻觉」——很多人以为「数据库有日志,随时能回滚」,这一段就是反例。

import os
import sqlite3
import tempfile

db = os.path.join(tempfile.gettempdir(), "school.db")
c = sqlite3.connect(db)
c.executescript("""
    CREATE TABLE students (id INTEGER PRIMARY KEY, name TEXT,
                           class_id INTEGER);
    INSERT INTO students VALUES (1, '张伟', 1), (2, '李娜', 1),
                                (3, '王强', 2), (4, '赵敏', 2),
                                (5, '孙悦', 1);
""")
c.commit()
print("原来", c.execute("SELECT COUNT(*) FROM students").fetchone())

# 一句 delete 删掉一半,然后提交——这时候 rollback 已经没用了
c.execute("DELETE FROM students WHERE class_id = 1")
c.commit()
print("删完", c.execute("SELECT COUNT(*) FROM students").fetchone())

# rollback 救不回来,因为已经提交了
c.rollback()
print("rollback 之后", c.execute("SELECT COUNT(*) FROM students").fetchone())

实跑输出:

--------------------------------------------------------------------
第七十八课:数据备份——唯一能在「删错了」之后救你的东西
--------------------------------------------------------------------

  先说清楚:事务救不了你
  SQL> SELECT COUNT(*) AS 学生数 FROM students
  学生数
  -------
  5
  (1 行)
  SQL> DELETE FROM students WHERE class_id = 1
  影响 3 行
  SQL> SELECT COUNT(*) AS 学生数 FROM students
  学生数
  -------
  2
  (1 行)
  **一句 `delete` 删掉了三个学生,然后 `commit` 了。** **这时候 `rollback` 已经没用了**——
  **第 15 章说过:提交之后就回不去了。**
  **所以「删错了怎么办」这个问题,事务的答案是「没法办」,备份的答案是「把昨天那份拿回来」。**
  **这两件事的分工要分清**:**事务防「改一半」,备份防「改错了」。**

  逻辑备份和物理备份
  **逻辑备份**:**把数据导成「一串 SQL 语句」**,
  **比如一堆 `insert`。** **MySQL 的 `mysqldump` 就是干这个的。**
  **物理备份**:**直接把数据文件拷走**,
  **不管里面是什么格式。** **本沙箱的「备份」就是拷 `.db` 文件,属于物理备份。**
  **两者的取舍很清楚**:
  **逻辑备份的好处是「能看懂、能改、能跨版本、能只挑一部分」**——
  **导出来是个文本文件,`grep` 一下就知道有没有你要的数据**;
  **坏处是「慢」**,**大库导一次要几小时,还原更慢(要重新执行几百万条 `insert`)。**
  **物理备份的好处是「快」**(拷文件而已),
  **坏处是「绑版本、绑平台」**——
  **MySQL 5.7 的数据文件不能直接给 8.0 用,Windows 上的也不能直接给 Linux 用。**
  **所以常规做法是「物理备份做日常、逻辑备份做归档」**:
  **每天拷文件(快、能快速恢复),每个月导一次 SQL(留个能跨版本的底)。**

  全量备份和增量备份
  **全量**:**每次备份整份数据。** **简单、恢复快(只要一份),但占空间、耗时间。**
  **增量**:
  **只备份「上次备份之后变化的部分」。** **省空间、快,但恢复麻烦**——
  **要先恢复全量,再按顺序把每个增量叠上去,中间缺一个就恢复不出来。**
  **MySQL 的增量靠 `binlog`**(二进制日志):**它记着每一条改动数据的语句**,
  **所以「恢复到今天下午三点」这种需求,靠的是「全量 + 重放 binlog 到那个时刻」。**
  **这也是「误删数据」最常见的救命手段。**
  **本沙箱没有 binlog**,**只有「拷文件」和「导 SQL」两招**。
  **所以这里的「增量」只能自己想办法**:
  **比如每天导一份、按日期存着,要回哪一天就用哪一份。** **粗糙,但够用。**

  备份的三条铁律
  **第一条:备份要能恢复,而且你演练过。** **「备份文件存在」不等于「能恢复」**——
  **文件可能是 0 字节的、可能是半截的、可能权限不对读不了。**
  **所以定期要真恢复一次到测试库,看数据对不对。** **没演练过的备份,等于没有备份。**
  **第二条:备份要在另一台机器上。** **备份和原库放在同一块硬盘上,硬盘坏了两个一起没。**
  **放在同一台机器上,机器被勒索病毒加密了,备份一起被加密。**
  **这一条每年都有人用血泪重新发现一次。**
  **第三条:备份要定期、要留多份。** **每天一份、留三十天**,
  **这样「上周三误删了、今天才发现」还有救。** **只留最新一份的话,错误数据会把好数据覆盖掉**——
  **等你发现时,好数据已经不在任何一份备份里了。**

原来 5 行,删完 2 行。 rollback() 之后还是 2 行——因为那些改动已经 commit 过了,回滚没有东西可回。 这就是「事务不是后悔药」的现场证明。

30 秒实验

  1. 把 c.commit() 挪到 rollback() 之后再试一次,结果会怎样?
  2. 为什么「物理备份能快速恢复」但「不能跨版本」?
  3. 只留最新一份备份,什么情况下会「连备份一起坏掉」?

常见误区

  1. 以为有事务就不用备份。 commit 之后回不去,改错了只能靠备份。
  2. 备份和原库放在同一台机器。 硬盘坏、勒索病毒都会一起带走。
  3. 只留最新一份备份。 错误数据会把好数据覆盖掉,发现时就晚了。
自测三问(点开对答案) 1. 事务和备份分别防哪种错? —— 事务防「改一半」(原子性),备份防「改错了」(已提交的误操作)。两者不互相替代。 2. 逻辑备份和物理备份各适合什么场景? —— 逻辑备份适合长期归档和跨版本迁移(能看懂、能改、慢); 物理备份适合日常快速备份与恢复(快、绑版本平台)。 3. 「没演练过的备份等于没有备份」怎么理解? —— 备份文件可能 0 字节、半截、权限不对读不了。只有真恢复一次到测试库、数据对得上,才算有备份。

知道备份是怎么回事了,下一篇动手:17.2 数据库的备份与还原。

相关笔记