🛢️ 第6章 数据表操作
📄 一句话概括

CREATE TABLE ... LIKE 只抄结构,AS SELECT 只抄数据,没有一种两样都抄全。 AS SELECT 抄出来的是一张裸表:约束、索引、默认值、自增全丢。 想复制一张一样的表用 LIKE,想把查询结果落成表用 AS SELECT。

📝 本章和前面几章的关系

6.4 讲的是「换个名字」,这一篇讲「再要一张」。两件事经常连着用。 它是「备份表」和「在线切换」两套手法的起点:先复制,再切。 这一篇的 AS SELECT,在第 20 章讲数据备份时会当成「逻辑备份」的一种用法。

一、只抄结构:CREATE TABLE ... LIKE

LIKE 抄的是「建表语句里除数据之外的一切」。

CREATE TABLE tb_bak LIKE tb_order;

具体抄哪些、不抄哪些,教材 6.5 列过,这里按「会不会踩坑」重排:

项 LIKE 抄不抄
列名与类型 抄
NOT NULL、DEFAULT、CHECK 抄
索引(含唯一索引) 抄
AUTO_INCREMENT 的当前值 抄(接着源表往下走)
外键约束 不抄
数据 不抄

第四行是很多人想不到的:连「下一行该给几号」都抄过来了。 所以抄出来的表第一行 id 不是 1,是源表的下一个值。 做测试数据时这反而添麻烦,想要从 1 开始得手动 ALTER TABLE ... AUTO_INCREMENT = 1。 第五行那个「不抄外键」也有道理:外键指向的是另一张表,抄过来还是指着原来那张,没意义。 真要抄外键得手工写。

二、只抄数据:AS SELECT

AS SELECT 抄的是「这条查询的结果」,顺带按结果集推一个表结构。

CREATE TABLE tb_bak AS SELECT * FROM tb_order;
CREATE TABLE tb_bak AS SELECT * FROM tb_order WHERE created < '2026-10-01';

第一句是整表抄,第二句是条件抄。两句的差别只是 WHERE,但第二句用处大得多。 它抄出来的表是这样的:列名和类型按结果集推,其余全没有。

项 AS SELECT 抄不抄
列名 抄
类型 按结果集推,可能和源表不一致
NOT NULL/DEFAULT/CHECK 不抄
索引与唯一约束 不抄
AUTO_INCREMENT 不抄,从 1 开始

第二行那个「可能不一致」要举个例子:源表 DECIMAL(8,2) 的列, 抄过来可能变成 DECIMAL(10,2)。 因为结果集推类型时按实际值算,不照抄原定义。 这一点在做「数据快照」时没问题,在做「表结构复制」时就是坑。

三、两种抄法丢了什么

把两张表并排看,结论很清楚。

项 LIKE AS SELECT
数据 不抄 抄
列与类型 抄 抄(按结果集推)
索引与约束 抄 不抄
自增计数器 抄 不抄
执行速度 快 取决于数据量

注意最后一行:LIKE 不碰数据,所以再大的表也是秒回。 而 AS SELECT 要把数据读一遍写一遍,百万行就是几秒到几十秒。 还有一句必须提醒:AS SELECT 抄出来的表没有任何约束,所以它可以塞进重复主键、空值、负价格。 如果这张表要留作对比、事后还要拿来对账,那它必须自带约束,否则脏数据会混进来。

四、复制表的四个真实用途

教材 6.5 只讲语法,实际用起来是这四件事。

用途 用哪种抄法 注意什么
改结构前留一份 LIKE + 补数据,或 AS SELECT 要能还原,别只抄结构
按日期做快照 AS SELECT 带 WHERE 快照表没约束,只当只读
在线切换的新表 LIKE 再灌新数据 灌完用 RENAME TABLE 换名
给别人一份测试数据 AS SELECT 带 WHERE 抽样 注意脱敏

第三行是「不停机改结构」的核心手法,值得记住顺序: LIKE 建新表 → 灌数据 → 建索引 → RENAME TABLE 换名。 第四行那个「脱敏」是真事。 把生产数据抄一份给测试,手机号、身份证号都在里面,这属于数据泄露。 正确做法是先抄到临时表,再用 UPDATE 把敏感列打码,最后才给出去。

五、动手 Demo:抄一张表,两种抄法各丢了什么

这一课先建一张带六种约束的源表(50 行),再分别用两种抄法抄一遍,逐项对比。 沙箱里 AS SELECT 能原样跑,LIKE 没有,用「取建表语句换表名」来等价。 最后会看到一件吓人的事:AS SELECT 抄出来的表连重复主键都能塞进去。

# 源表:自增主键 + 唯一 + 非空 + 默认 + CHECK + 一个二级索引,50 行
c.execute("CREATE TABLE tb_src (id INTEGER PRIMARY KEY AUTOINCREMENT, "
          "code TEXT NOT NULL UNIQUE, price REAL NOT NULL DEFAULT 0 CHECK (price >= 0))")
c.execute("CREATE INDEX i_price ON tb_src (price)")

# 一、只抄结构:取建表语句、换表名,索引要手工补
ddl = c.execute("SELECT sql FROM sqlite_master WHERE name = 'tb_src'").fetchone()[0]
c.execute(ddl.replace("CREATE TABLE tb_src", "CREATE TABLE tb_like"))
c.execute("CREATE INDEX i_like_price ON tb_like (price)")

# 二、只抄数据:一句就够,但约束全丢
c.execute("CREATE TABLE tb_as AS SELECT * FROM tb_src")

# 三、验证:往新表里插两条一模一样的主键
c.execute("INSERT INTO tb_as (id, code, price) VALUES (1, 'DUP', 1)")
c.execute("INSERT INTO tb_as (id, code, price) VALUES (1, 'DUP', 1)")

实跑输出:

--------------------------------------------------------------------
第二十九课:抄一张表,有两种抄法
--------------------------------------------------------------------

  **教材 6.5 讲复制表。两种写法名字很像,抄到的东西差很远。**
  **一种只抄结构,一种只抄数据,没有一种「两样都抄全」的。**
  **这一课把两种都跑一遍,然后逐项对比新表里剩下什么。**

  一、准备一张有约束的源表
  --------------------------------------------------------------------
  **源表有:自增主键、唯一约束、非空、默认值、CHECK、一个二级索引、50 行数据。**
  **记住这七样,下面看两种抄法各自丢了多少。**

  二、只抄结构:CREATE TABLE ... LIKE
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | CREATE TABLE tb_like LIKE tb_src;
  ② 教材原码,本沙箱不执行

  **沙箱里没有 `LIKE` 这个写法,等价做法是取源表的建表语句、换个表名再执行。**
  **我照这个做法抄了一遍:**

  项               tb_src   tb_like
  ----------------------------------------------------
  列数             3        3
  行数             50       0
  索引             i_price  i_like_price(我手工补的)
  插一行拿到的 id  51       1
  **第三行那个「手工补的」是一条真差别。**
  **真 MySQL 的 `CREATE TABLE ... LIKE` 会把索引一起抄过去,这里不会,得自己补。**
  **第四行也是真差别:MySQL 的 `LIKE` 会连 `AUTO_INCREMENT` 的当前值一起抄。**
  **所以抄出来的表下一行不会是 1,而是源表的下一个值。**
  **这一点在「复制一张表做测试」时反而会添麻烦。**

  三、只抄数据:CREATE TABLE ... AS SELECT
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | CREATE TABLE tb_as AS SELECT * FROM tb_src;
  ③ 教材原码,本沙箱不执行


  mysql> CREATE TABLE tb_as AS SELECT * FROM tb_src
  **这一句在沙箱里能原样跑。它抄出来的是什么?先看行数和索引:**

  项        tb_src    tb_as
  ----------------------------
  行数      50        50
  索引个数  2         0
  code 列   不能为空  可以为空
  **索引个数是 0——一个都没有。**
  **新表连唯一约束都没了,所以 `code` 列变成了「随便重复」。**
  **这一点不是理论风险,我试了一下:往新表里插两条一样的 code。**

  动作                       结果
  -----------------------------------------
  插两条完全相同的行         两条都插进去了
  现在 tb_as 里还剩几行同名  2 行
  **这就是 `AS SELECT` 的本质:它只搬数据,所有约束、索引、默认值、自增全丢掉。**
  **它抄出来的是一张「裸表」,什么都不管。**

  四、两种抄法的四个差别
  --------------------------------------------------------------------

  项              `LIKE`                `AS SELECT`
  ----------------------------------------------------------
  数据            不抄                  抄
  列与类型        抄                    抄(类型按结果集推)
  索引与唯一约束  抄                    不抄
  自增计数器      抄(接着源表往下走)  不抄
  **四行看完,结论就一句:想复制一张「一样的表」用 `LIKE`。**
  **想把查询结果落成一张表,用 `AS SELECT`。**
  **前者是建表,后者是存结果。**
  **`AS SELECT` 真正的用处是配合 `WHERE` 做快照:**
  **`CREATE TABLE tb_bak_1030 AS SELECT * FROM tb_order WHERE created < '2026-10-01'`。**
  **这种「每月一号存一份上月数据」的用法,比导出成文件再导入省事得多。**

第一段那张对比表:列数都是 3,行数是 50 和 0。 LIKE 抄了结构不抄数据,所以 0 行。 索引那一行我要诚实说明:i_like_price 是我手工补的,SQLite 的等价做法不会自动带索引。 第四行「插一行拿到的 id」是 51 对 1。 这一格是真差别:真 MySQL 的 LIKE 会把 AUTO_INCREMENT 当前值一起抄, 所以抄出来的表下一行是 51,不是 1。 这里的表是空的、计数器是新的,所以拿到 1。 第二段:AS SELECT 抄出来 50 行,索引 0 个,code 列从「不能为空」变成「可以为空」。 一个约束都没剩。 第三段最吓人:往 tb_as 里插两条完全相同的 (1, 'DUP', 1),两条都进去,表里 2 行同名。 因为新表没有主键、没有唯一约束——它只是一堆列。 所以「抄一份做备份」如果用 AS SELECT,这份备份本身可能是脏的。

30 秒实验

  1. 给 AS SELECT 那句加上 WHERE price > 10,看新表有几行。源表 50 行里价格大于 10 的那些。 这就是「按条件做快照」的用法,比导出成文件再导入省事。
  2. 把 LIKE 抄出来的表插满 50 行,再看下一行的 id 是 51 还是 101。 在这里是 51,因为计数器从头开始;真 MySQL 的 LIKE 会从源表的下一个值开始。
  3. 想一想:用 AS SELECT 抄一张 1000 万行的表,磁盘和耗时会是什么量级? 数据要写一遍,索引一个都没有,所以比原表小;耗时主要是读加写,几分钟到十几分钟。

常见误区

  1. 「AS SELECT 就是复制表,抄完是一样的。」 不一样。它只抄数据,所有约束、索引、默认值、自增全丢。 抄出来的是一张裸表。
  2. 「LIKE 会把数据一起抄过来。」 不会。它只抄结构,抄完是 0 行。 要带数据得再一句 INSERT ... SELECT。
  3. 「复制一份表给测试用,直接抄就行。」 手机号、身份证、地址都在里面,这属于数据泄露。 要先脱敏再给。
  4. 「LIKE 抄出来的表和原表完全一样。」 外键不抄,AUTO_INCREMENT 的当前值会抄过来,这两处都和直觉不同。
自测三问(点开对答案) 1. 要一张「结构一样、数据不要」的空表,用哪一句? —— **`CREATE TABLE 新表 LIKE 旧表`。** **它抄结构、索引、约束,不抄数据。** 2. `AS SELECT` 抄出来的表为什么能插重复主键? —— **因为它没抄主键约束,连索引都没有。** **它就是一堆列,什么都不管。** 3. 在线给大表改结构,`LIKE` 在里面起什么作用? —— **它负责第一步:建一张结构一样的新表。** **后面灌数据、建索引,最后 `RENAME TABLE` 换名。**

6.5 到这儿结束。下一篇 6.6 讲删除表—— 「删」有三种,代价和后果都不一样,最后那一篇会把三者摆在一起。

相关笔记