MySQL 只备几张表:mysqldump -u 用户 -p 库 表1 表2 > 文件.sql。
库内快照用 create table 新表 like 旧表 加 insert … select。
create table 新表 as select * from 旧表 有个大坑:它只带数据,不带任何约束——
主键、唯一键、非空、check 全没了,而且你看不出来。
本沙箱里跨库靠 attach;备份一张表除了数据和建表语句,还有索引和触发器要一起拷。
上一篇备份整库,这一篇只备份一张表——实际工作里「只备一张表」比「备整库」更常见:
改一张表之前先存个底,改坏了拿回来。
这一篇有个很值得记住的坑:
create table … as select 丢约束,而且丢了不报错。
这种「静默降级」是最难发现的一类问题。
一、MySQL 里怎么只备份一张表
mysqldump -u root -p mydb students classes > two_tables.sql——
库名后面直接跟表名,就是只导这几张。
库内快速存个底:
create table students_bak as select * from students;
一句话搞定,但有个大坑,下一节就讲。
可靠的做法是两步:先用 create table 新表 like 旧表 把结构复制过来(索引和约束都在,
外键除外),再 insert … select 灌数据。 like 这一句是 MySQL 特有的,很方便。
二、这里的第一种写法:attach 一个目标库再拷
attach database '源库文件' as src 之后,
就能用 src.表名 访问它。 于是「拷一张表到另一个库」就是一句 create table … as select。
Demo 里五行都拷过来了,看起来没问题。 但看它建出来的表结构:id INT、name TEXT、
height REAL、class_id INT——INTEGER 变成了 INT,primary key 那句不见了。
pragma table_info 更直接:最后一列 pk 全是 0。 也就是说:主键丢了。
为什么? create table … as select 只根据「结果集的列」推断类型,不复制任何约束。
主键、唯一键、非空、默认值、check、外键,一个都不带过来。
这是 SQL 标准里的行为,不是某个数据库的 bug。
后果很实在:这张备份表可以插重复的主键、可以插空值,
拿它当「原表」用会静默出错。 更麻烦的是「它看起来像一张正常的表」,你不会发现少了什么。
三、正确做法:先建表,再灌数据
手写一遍 create table,再 insert into … select * from。
Demo 里这样做之后 pk 那一列是 1 了,主键在。 代价是要手抄结构,累一点但结果对。
更好的办法是从原表把建表语句取出来直接用:
select sql from sqlite_master where name = 'students',
把表名改掉、执行一遍。 这样主键、唯一键、check、默认值全都原样带过来,一个不漏。
MySQL 那边就是 create table 新表 like 旧表,
一句话顶这一整套。 不过它不复制外键——
要连外键一起,得自己补 alter table … add constraint。
四、跨库拷表:attach 是那把钥匙
attach database '另一个文件' as 别名 之后,
用 别名.表名 就能访问。 这是本沙箱里唯一「跨库操作」的办法——
没有 mysqldump,也没有 select … into outfile。
连 sqlite_master 都能带库名访问:
select name from src.sqlite_master where type = 'table',
「那个库里有什么表」一句话就查出来了。 迁移数据时非常有用。
两个细节要记住:同时最多挂 10 个库(编译时可以调大);
事务是跨库的——
一个事务里改了两个挂上来的库,rollback 会一起撤。 这一点和第 15 章讲的事务是打通的。
五、备份一张表,别忘了表之外的东西
一张表不只是「列 + 行」,
它还有:索引、触发器、check 约束、默认值、注释。 只把数据拷过去,这些东西都没了。
所以「备份一张表」完整地说要拷三样:建表语句(sqlite_master 里的 sql)、
索引语句(type = 'index' 那几行)、触发器语句(type = 'trigger' 那几行)。
少拷一样,恢复出来的表就是「残缺」的。
Demo 里查 tbl_name = 'students' 只列出一个 table——
因为那张表上没建索引、没建触发器。 如果建过,这里会多出几行,那几行也要一起备份。
「备份」这件事,粒度永远比想象中细。
整库备份没这个问题(iterdump 会把索引一起导出来);只备一张表才有。
这也算是「图省事」的代价。
六、动手 Demo:用 attach 拷一张表,看主键怎么丢的、怎么补回来
Demo 五段:挂上源库、用 create table as select 拷一份并查结构、手写建表再灌一次、
把原表的建表语句取出来、最后看「表之外」还有什么要备份。
第二段那个 pk 全是 0 的表格,是这一篇的核心证据——数据一行不少,结构已经废了。
import sqlite3
import _q46 as Q
c = Q.con()
# 1) 另一条连接挂上源库和备份库
b = sqlite3.connect("bak.db")
b.execute("ATTACH DATABASE 'school.db' AS src")
# 2) 一句 create table as select:数据齐了,结构丢了一半
Q.run(b, "CREATE TABLE s_bad AS SELECT * FROM src.students")
Q.show(b, "PRAGMA table_info(s_bad)") # pk 一列全是 0
# 3) 正确做法:先照原样建表,再 insert select
Q.run(b, "CREATE TABLE s_good (id INTEGER PRIMARY KEY, name TEXT, "
"height REAL, class_id INTEGER)")
Q.run(b, "INSERT INTO s_good SELECT * FROM src.students")
Q.show(b, "PRAGMA table_info(s_good)") # pk 是 1
# 4) 更好:把原表的建表语句取出来直接用
Q.show(b, "SELECT sql FROM src.sqlite_master WHERE name = 'students'")
# 5) 表之外的东西:索引、触发器也要备份
Q.show(b, "SELECT type, name FROM src.sqlite_master "
"WHERE tbl_name = 'students' ORDER BY type")
实跑输出:
--------------------------------------------------------------------
第八十课:表的备份与还原
--------------------------------------------------------------------
MySQL 里怎么只备份一张表
MySQL 原码(本沙箱不执行):
| # 只备份指定的两张表
| mysqldump -u root -p mydb students classes > two_tables.sql
|
| # 库内直接拷一份(快,但只在本库)
| CREATE TABLE students_bak AS SELECT * FROM students;
|
| # 或者先建表再灌数据(能保住约束)
| CREATE TABLE students_bak LIKE students;
| INSERT INTO students_bak SELECT * FROM students;
**`create table 新表 as select * from 旧表` 是最省事的一句**,
**但它有个大坑——下一篇就是讲这个。**
**可靠的做法是两步**:**先用 `create table 新表 like 旧表` 把结构复制过来**(索引和约束都在,
外键除外),**再 `insert … select` 灌数据。**
这里的第一种写法:attach 一个目标库再拷
SQL> CREATE TABLE s_bad AS SELECT * FROM src.students
完成
SQL> SELECT COUNT(*) AS 行数 FROM s_bad
行数
-----
5
(1 行)
**五行都拷过来了,看起来没问题。** **但看看它建出来的表结构:**
CREATE TABLE s_bad(
id INT,
name TEXT,
height REAL,
class_id INT
)
SQL> PRAGMA table_info(s_bad)
cid name type notnull dflt_value pk
--------------------------------------------------------------------
0 id INT 0 NULL 0
1 name TEXT 0 NULL 0
2 height REAL 0 NULL 0
3 class_id INT 0 NULL 0
(4 行)
**注意两处**:**`INTEGER` 变成了 `INT`——原来「`id INTEGER PRIMARY KEY`」那一句,只剩下了类型**;
**`pragma table_info` 最后一列 `pk` 全是 0。** **也就是说:主键丢了。**
**为什么?** **因为 `create table … as select` 只根据「结果集的列」推断类型,不复制任何约束。**
**主键、唯一键、非空、默认值、`check`、外键,一个都不带过来。**
**这是 SQL 标准里的行为,不是 SQLite 的 bug。**
**后果很实在**:
**这张备份表可以插重复的主键、可以插空值,拿它当「原表」用会静默出错。**
**更麻烦的是——它看起来像一张正常的表,你不会发现少了什么。**
正确做法:先建表,再灌数据
SQL> CREATE TABLE s_good (id INTEGER PRIMARY KEY, name TEXT, height REAL, class_id INTEGER)
完成
SQL> INSERT INTO s_good SELECT * FROM src.students
影响 5 行
SQL> SELECT COUNT(*) AS 行数 FROM s_good
行数
-----
5
(1 行)
SQL> PRAGMA table_info(s_good)
cid name type notnull dflt_value pk
-----------------------------------------------------------------------
0 id INTEGER 0 NULL 1
1 name TEXT 0 NULL 0
2 height REAL 0 NULL 0
3 class_id INTEGER 0 NULL 0
(4 行)
**这次 `pk` 那一列是 1 了**,主键在。
**代价是要手写一遍 `create table`**——**累一点,但结构是对的。**
**更好的办法是从原表把建表语句取出来直接用**:
**`select sql from sqlite_master where name = 'students'`**,
**把表名改掉、执行一遍。**
**这样主键、唯一键、`check`、默认值全都原样带过来,一个不漏,也不用自己抄。**
**MySQL 那边就是 `create table 新表 like 旧表`**,**一句话顶这一整套。**
跨库拷表:attach 是那把钥匙
**`attach database '另一个文件' as 别名`**,
**之后就能用 `别名.表名` 访问它。** **这是本沙箱里唯一「跨库操作」的办法**——
**没有 `mysqldump`,也没有 `select … into outfile`。**
SQL> SELECT name FROM src.sqlite_master WHERE type = 'table'
name
---------
classes
students
(2 行)
**注意 `src.sqlite_master` 这个写法**:**`sqlite_master` 也能带库名访问**,
**所以「那个库里有什么表」一句话就查出来了。** **迁移数据时非常有用。**
**`attach` 还有两个细节**:**同时最多挂 10 个库**(编译时可以调大);
**事务是跨库的**——
**一个事务里改了两个挂上来的库,`rollback` 会一起撤。**
**这一点和第 15 章讲的「事务」是打通的。**
备份一张表之后,别忘了补上「表之外」的东西
**一张表不只是「列 + 行」**,
**它还有:索引、触发器、`check` 约束、默认值、注释。** **只把数据拷过去,这些东西都没了。**
**所以「备份一张表」完整地说要拷三样**:**建表语句**(`sqlite_master` 里的 `sql`)、
**索引语句**(`type = 'index'` 那几行)、**触发器语句**(`type = 'trigger'` 那几行)。
**少拷一样,恢复出来的表就是「残缺」的。**
SQL> SELECT type, name FROM sqlite_master WHERE tbl_name = 'students' ORDER BY type
type name
-------------------
table students
(1 行)
**这里只列出一个 `table`**——
**因为 `students` 上没建索引、没建触发器。** **如果建过,这里会多出几行**,
**那几行也要一起备份。** **「备份」这件事,粒度永远比想象中细。**
第一段 s_bad 拷过来 5 行,但建表语句变成 id INT,pk 那一列全是 0。
第二段手写建表后 s_good 的 pk 是 1,主键回来了。
第三段能直接取出原表的建表语句,这就是最省事又最可靠的做法。
第五段 tbl_name = 'students' 只有一行 table——
这张表上没索引、没触发器,所以没有别的要拷。
30 秒实验
- 给
students建一个索引和一个触发器,再跑第五段,会多出几行? create table as select拷过去的表能插重复主键吗?试一下。- 为什么
create table 新表 like 旧表不复制外键?这会造成什么问题?
常见误区
- 用
create table as select当「备份」。 约束全丢,而表面上看不出来,最危险。 - 只备数据不备建表语句。 恢复出来的表类型都变了(
INTEGER变INT),主键也没了。 - 忘了索引和触发器。 表能查但慢、自动逻辑也没了,备份要做到「表之外」那几行。
自测三问(点开对答案)
1. `create table 新表 as select * from 旧表` 会丢什么? —— 所有约束:主键、唯一键、非空、默认值、check、外键。它只按结果集推断列类型,而且不会报错。 2. 本沙箱里跨库拷表靠什么? —— `attach database '文件' as 别名`,然后用 `别名.表名` 访问;连 `sqlite_master` 也能带库名查。 3. 备份一张表除了数据还要拷什么? —— 建表语句、索引语句、触发器语句三样。少一样恢复出来就是残缺的。第 17 章完了: 备份的理由、整库备份、单表备份,三件都过了。 下一篇进第 18 章——其他内容:系统库、分页、表的设计。
- ☐ (本篇完,下一篇 18.1 系统数据库)