MySQL 用 mysqldump -u 用户 -p 库名 > 文件.sql 备份,
mysql -u 用户 -p 库名 < 文件.sql 还原。 它们是命令行程序,不是 SQL 语句。
本沙箱的 iterdump() 就是这里的 mysqldump:返回一串「能重建整个库」的 SQL,
用 executescript 灌回去就还原了。
直接拷 .db 文件不总是安全(可能拷到半截);c.backup(d) 才是「在线一致备份」。
上一篇讲「为什么」,这一篇讲「怎么做」,而且两边都真跑。
iterdump 这个 API 很值得记住:它能让你在没有任何外部工具的情况下,把整个库导成文本。
排查问题、做测试数据、迁移环境,都用得上。
一、MySQL 里长什么样
备份:mysqldump -u root -p mydb > mydb_20261101.sql;
还原:mysql -u root -p mydb < mydb_20261101.sql。
两个都是命令行程序,不是 SQL 语句——
所以它们写在 >/< 前后,而不是在客户端里敲。 这一点常让人困惑。
还原之前要先把库建出来(create database mydb)——
mysqldump 默认不含 create database 那一条,
要含就得加 --databases 参数。
不加它的话,mysql < dump.sql 会报「no database selected」。
两个常用参数:--no-data 只导结构(建空库用)、
--no-create-info 只导数据(往已有的表里灌)。
这两个参数配合起来,就是「表结构变更」和「数据迁移」分开做的基础。
二、本沙箱的 mysqldump:iterdump
list(c.iterdump()) 返回一串字符串,
就是「重建这个库需要的全部 SQL」。 Demo 里那个库是 11 行。
结构很清楚:第一行 begin transaction、最后一行 commit;
每条 create table 后面紧跟着它的 insert。
为什么要包在 begin transaction 里? 因为「导到一半出错」时,要能整体作废,
不能留下一个「表建了、数据只灌了一半」的库。 这和事务的原子性是一回事。
表名被双引号包起来了(insert into "students")——这是为了防表名里有特殊字符(空格、
关键字)。 所以你导出的文本里会看到一堆双引号,那是正常的。
它就是本沙箱版的 mysqldump:
一样的思路——把数据变成「能重新执行一遍的 SQL」。 理解了这一点,mysqldump 也就不神秘了。
三、还原:把那串 SQL 执行一遍
r.executescript("\n".join(c.iterdump())),一行就还原完了。
Demo 里还原到空库,五行学生、两个班都在。
注意这是还原到一个「空库」里。
如果目标库里已经有同名表,create table 会报「already exists」。
所以还原的正确姿势是:
要么还原到一个全新的库,要么先 drop 掉旧的。
直接往生产库上 executescript 一份 dump,是会把东西撞坏的。 mysqldump 那边同理——
它的 dump 里也没有 drop table if exists,要加得自己写 --add-drop-table。
四、热备份:源库还在写也能拿到一致快照
直接拷 .db 文件有个大坑:
拷到一半源库正在写,拷出来的可能是「半截」的。 数据库文件不是普通文件,它不是「一个整体」
——中间可能有未提交的页、可能有日志还没合并。
Demo 里用 c.backup(d):它在源库上加短暂的读锁、把数据一页一页复制到目标库,
最后得到一份一致的快照。 源库在这期间可以继续读,写会等一小会儿。
结果里包含了刚插进去的那个学生,说明备份拿到的不是「旧快照」,
而是「备份开始那一刻的一致状态」。
MySQL 那边的对应物是 xtrabackup(物理热备份,不锁表),
不用它的话「拷数据文件」必须停服务。 所以「热备份工具」这件事,两个数据库都需要。
五、三种备份方式,怎么选
拷文件:最快、最简单,
但必须停服务,或者确认没人在写。 适合「小工具、单机、夜里没人用」的场景。
iterdump 导 SQL:慢,但能看懂、能改、能跨版本。
适合「留一份能长期保存的底」,也适合「只捞其中几张表的数据」。
backup() 在线备份:又快又一致,运行中也能做。
适合「服务不能停」的场景——这是默认该用的那一个。
一句话:
能停服务就拷文件,不能停就用 backup(),要长期归档就导 SQL。
三种都做一遍也不费多少事,关键是「真的要能恢复」。
六、动手 Demo:iterdump 当 mysqldump,backup 当热备份
Demo 三段:iterdump() 导出一串 SQL 并打印前几行、executescript 还原到空库、
backup() 做一次在线备份。
第一段那个「11 行」很值得数一遍——
begin 一行、两条 create table、七条 insert、commit 一行。
导出文件的大小,大致就按这个比例估。
import sqlite3
import _q46 as Q
c = Q.con()
# 1) 像 mysqldump 一样,把整库导成一串 SQL
lines = list(c.iterdump())
print("一共 %d 行" % len(lines))
for ln in lines[:7]:
print(" " + ln)
# 2) 还原:把那串 SQL 灌进一个空库
r = sqlite3.connect("restore.db")
r.executescript("\n".join(c.iterdump()))
Q.show(r, "SELECT COUNT(*) AS 学生数 FROM students")
# 3) 热备份:源库还在写,也能拿到一致快照
d = sqlite3.connect("hot.db")
c.execute("INSERT INTO students (name, height, class_id) "
"VALUES ('周杰', 172.0, 1)")
c.commit()
c.backup(d)
Q.show(d, "SELECT COUNT(*) AS 备份里的学生数 FROM students")
实跑输出:
--------------------------------------------------------------------
第七十九课:数据库的备份与还原
--------------------------------------------------------------------
MySQL 里长什么样
MySQL 原码(本沙箱不执行):
| # 备份整个库(导出成 SQL 文本)
| mysqldump -u root -p mydb > mydb_20261101.sql
|
| # 还原
| mysql -u root -p mydb < mydb_20261101.sql
|
| # 只备份结构,不带数据
| mysqldump -u root -p --no-data mydb > mydb_schema.sql
|
| # 只备份数据,不带建表语句
| mysqldump -u root -p --no-create-info mydb > mydb_data.sql
**`mysqldump` 是个命令行程序,不是 SQL 语句**——
**所以它写在 `>` 前面,输出重定向到文件。** **这一点常让人困惑:它不是你在客户端里敲的东西。**
**还原用 `mysql` 客户端加 `<` 重定向**,
**或者进客户端之后 `source 文件名`。** **注意还原之前要先把库建出来**
(`create database mydb`)——**`mysqldump` 默认不含 `create database` 那一条**,
**要含就得加 `--databases` 参数。**
本沙箱的「mysqldump」:iterdump
**`c.iterdump()` 返回一组字符串,就是「重建这个库需要的全部 SQL」。**
**一共 11 行,前面几行是这样:**
BEGIN TRANSACTION;
CREATE TABLE classes (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO "classes" VALUES(1,'一班');
INSERT INTO "classes" VALUES(2,'二班');
CREATE TABLE students (id INTEGER PRIMARY KEY, name TEXT,
height REAL, class_id INTEGER);
INSERT INTO "students" VALUES(1,'张伟',175.5,1);
INSERT INTO "students" VALUES(2,'李娜',162.0,1);
……(后面还有 4 行)
**注意三件事**:
**第一行是 `begin transaction`、最后一行是 `commit`**(**保证导出的东西是「一个整体」**);
**每条 `create table` 后面紧跟着它的 `insert`**;
**表名被双引号包起来了**(`insert into "students"`)——**这是为了防止表名里有特殊字符。**
**它就是本沙箱版的 `mysqldump`**:**一样的思路——把数据变成「能重新执行一遍的 SQL」。**
还原:把那串 SQL 执行一遍
**`executescript(整段 dump)`**,一行代码就还原完了。
SQL> SELECT COUNT(*) AS 学生数 FROM students
学生数
-------
5
(1 行)
SQL> SELECT id, name FROM classes
id name
------------
1 一班
2 二班
(2 行)
**五行学生、两个班都在。** **注意这是还原到一个「空库」里**——
**如果目标库里已经有同名表,`create table` 会报「already exists」。**
**所以还原的正确姿势是**:
**要么还原到一个全新的库,要么先 `drop` 掉旧的。**
**直接往生产库上 `executescript` 一份 dump,是会把东西撞坏的。**
热备份:源库还在写,也能拿到一份一致的快照
**直接拷 `.db` 文件有个大坑**:
**拷到一半源库正在写,拷出来的可能是「半截」的。**
**数据库文件和普通文件不一样,它不是「一个整体」**——
**中间可能有未提交的页、可能有日志还没合并。**
SQL> INSERT INTO students (name, height, class_id) VALUES ('周杰', 172.0, 1)
影响 1 行
**`c.backup(d)` 是「在线备份」**:**它会在源库上加短暂的读锁、把数据一页一页复制到目标库**,
**最后得到一份一致的快照。** **源库在这期间可以继续读,写会等一小会儿。**
SQL> SELECT COUNT(*) AS 备份里的学生数 FROM students
备份里的学生数
---------------
6
(1 行)
**六行——包括刚插进去的那个。** **这比「用文件管理器复制粘贴」可靠得多**,
**尤其是在有人正在用的时候。**
**MySQL 那边的对应物是 `xtrabackup`**(**物理热备份,不锁表**),
**不用它的话「拷数据文件」必须停服务。** **所以「热备份工具」这件事,两个数据库都需要。**
三种备份方式,怎么选
**拷文件**:**最快、最简单**,
**但必须停服务,或者确认没人在写。** **适合「小工具、单机、夜里没人用」的场景。**
**`iterdump` 导 SQL**:**慢,但能看懂、能改、能跨版本**。
**适合「留一份能长期保存的底」**,**也适合「只捞其中几张表的数据」。**
**`backup()` 在线备份**:**又快又一致**,
**运行中也能做。** **适合「服务不能停」的场景**——**这是默认该用的那一个。**
**一句话**:
**能停服务就拷文件,不能停就用 `backup()`,要长期归档就导 SQL。**
**三种都做一遍也不费多少事,关键是「真的要能恢复」。**
iterdump() 一共 11 行:
begin transaction + 两条 create table + 七条 insert + commit。
建表语句原样保留了换行和缩进,
所以打印出来会看到一行带缩进的 height REAL, class_id INTEGER);。
还原后 5 名学生、2 个班都在。
热备份拿到 6 行——包括备份前刚插的那个周杰。
30 秒实验
- 往同一个库连续
executescript两次同一个 dump,会报什么? iterdump()的结果里有没有drop table if exists?这会导致什么后果?- 为什么「直接拷文件」在有人写入时不可靠,而
backup()可靠?
常见误区
- 以为
mysqldump是 SQL 语句。 它是命令行程序,写在>前面,不能进客户端敲。 - dump 直接往生产库上灌。 里面有
create table,表已存在就报错, 要先建空库或加--add-drop-table。 - 用文件管理器拷正在被写的
.db。 可能拷到半截,要用backup()或先停写。
自测三问(点开对答案)
1. `iterdump()` 的结果为什么包在 `begin transaction` 里? —— 为了「导到一半出错时整体作废」,避免留下「表建了、数据只灌一半」的库。 2. `mysqldump` 还原前为什么要先建库? —— 它默认不包含 `create database` 语句,要包含得加 `--databases` 参数; 否则 `mysql < dump.sql` 会报没选库。 3. 为什么不能直接拷正在被写入的 `.db` 文件? —— 数据库文件不是「一个整体」,拷到一半可能有未提交的页或未合并的日志,得到的是不一致的半截。 要用 `backup()` 或停写。整库会备了,下一篇缩小到一张表:17.3 表的备份与还原。
- ☐ (本篇完,下一篇 17.3 表的备份与还原)