RENAME TABLE 能一次改多张、能跨库,而且多张之间是原子的。
ALTER TABLE ... RENAME TO 只能改一张、只能在同一个库里。
改名只动字典,所以再大的表也是秒回;但视图里的表名文本不会跟着改。
6.3 改的是列名,这一篇改的是表名——后者干净得多,因为它不碰数据。 它是「在线切换」这个手法的技术基础:新表建好、换名、旧表下线。 这一篇的两个写法,在第 6.6 节「建新表再换名」里还会用到。
一、两种写法,能力不一样
教材 6.4 讲的是 RENAME TABLE。它和第 6.3 节末尾那个 ALTER TABLE ... RENAME TO 是两回事。
RENAME TABLE tb_a TO tb_b; -- 一张
RENAME TABLE tb_a TO tb_b, tb_c TO tb_d; -- 多张,一次原子完成
RENAME TABLE db1.tb_a TO db2.tb_a; -- 跨库搬
ALTER TABLE tb_a RENAME TO tb_b; -- 只能一张、同库
| 写法 | 一次改几张 | 能跨库 | 语法位置 |
|---|---|---|---|
RENAME TABLE |
任意多张 | 能 | 独立语句 |
ALTER TABLE ... RENAME TO |
只能一张 | 不能 | ALTER 的一个子句 |
日常改名用哪个都行,需要批量或跨库才非 RENAME TABLE 不可。
但知道它有第三条能力的人不多:多张之间是原子的。
二、改名为什么是秒回
因为表名只存在数据字典里,改名就是把那个字符串换掉。 数据页、索引页、磁盘文件一个字节都不用挪。 这一点和「改列名」同理,和「改类型」完全相反。 所以「表名拼错了」这种事故,修复成本几乎为零;「字段类型选错了」才要命。 但秒回不等于没有副作用。 改名之后,所有「按表名硬编码」的东西都指着旧名字:视图、存储过程、触发器、应用程序里的 SQL。 数据库只负责自己那半边,应用程序那半边它管不了。
三、一次改多张是原子的
RENAME TABLE a TO b, c TO d 是「一个操作」,不是「两条语句」。
所以要么两张都改成,要么一张都不动。
这一条在发版时特别有用。
典型场景是「新表和旧表互换名字」:
-- 新数据已经灌进 tb_order_new
RENAME TABLE tb_order TO tb_order_old,
tb_order_new TO tb_order;
这两句一起执行,业务方看到的是「表名还是 tb_order,数据变了」,中间没有空窗。
如果拆成两句执行,中间那一瞬间 tb_order 是不存在的,所有查询都会报错。
这就是原子性的实际价值:不是「性能好」,是「不会出现中间态」。
在线变更的所有手法,本质都是在消灭中间态。
四、跨库搬表
RENAME TABLE db1.tb TO db2.tb 能把表从一个库移到另一个库。
在同一台服务器上,这一步也只是改字典,不搬数据。
但有两个前提要知道。
一是两个库要在同一个文件系统上(MySQL 8.0 用 InnoDB 通用表空间时基本都满足)。
二是目标库里不能有同名表。
如果不是「搬」而是「抄一份过去」,就得用第 6.5 节那两种复制法,再加一步删源表。
那样就不是原子的了,三步里任何一步失败都会留下两份。
五、改名之后,谁会坏
数据库里存「表名文本」的地方,改名时都不会自动更新。 教材没讲这一节,但这是改名真正会出事的地方。
| 对象 | 存的是文本还是引用 | 改名后 |
|---|---|---|
| 视图 | FROM 表名 这段文本 |
指旧名字,一查就报错 |
| 存储过程/函数 | 里面写的 SQL 文本 | 运行时报错 |
| 触发器 | ON 表名 这段文本 |
可能跟着表走,要看版本 |
| 外键约束 | 引用的是表 ID | 正常,不受影响 |
| 应用程序里的 SQL | 硬编码的表名 | 要改代码、要发版 |
第一行是真会出事的。
所以改名前先查一遍:information_schema.VIEWS 的 VIEW_DEFINITION 里搜表名。
第二行更隐蔽,因为它不是建表时就报错,是三个月后跑到那个存储过程才报。
一个稳妥的做法是:改名前先在测试库上改一遍,然后把相关功能点都跑一遍。
六、动手 Demo:改名:两个写法的差别,和它带坏的视图
这一课把两个写法、原子性、跨库搬表各跑一遍。
最后会看到一个和教材说法相反的实测结果,那个结果本身值得记住。
沙箱里没有 RENAME TABLE,多张改名用事务包住两条 ALTER 来等价。
# 一、改名:数据、索引都跟着走
c.execute("ALTER TABLE tb_old RENAME TO tb_new")
print(c.execute("SELECT COUNT(*) FROM tb_new").fetchone())
print(c.execute("PRAGMA index_list(tb_new)").fetchall())
# 二、视图会不会跟着改?(这一条结论反了)
print(c.execute("SELECT * FROM v_old").fetchall())
# 三、一次改两张,中途失败会怎样
c.execute("BEGIN")
c.execute("ALTER TABLE tb_a RENAME TO tb_b")
c.execute("ALTER TABLE tb_c RENAME TO tb_d") # 这里撞车
c.execute("ROLLBACK")
# 四、跨库搬表:ATTACH 挂第二个文件,三步走
c.execute("ATTACH DATABASE ? AS db2", (path2,))
c.execute("INSERT INTO db2.tb_move SELECT * FROM main.tb_move")
c.execute("DROP TABLE main.tb_move")
实跑输出:
--------------------------------------------------------------------
第二十八课:改名有两个写法,能力不一样
--------------------------------------------------------------------
**教材 6.4 讲 `RENAME TABLE`。它和第 6.3 节末尾那个 `ALTER TABLE ... RENAME TO` 是两回事。**
**一个能一次改多张、还能跨库搬;一个只能改一张、只能在同一个库里。**
**这一课把两种写法都跑一遍,再看改名之后依赖它的东西会怎样。**
一、两种写法摆一起
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| RENAME TABLE tb_a TO tb_b; -- 一张
| RENAME TABLE tb_a TO tb_b, tb_c TO tb_d; -- 多张,一次原子完成
| RENAME TABLE db1.tb_a TO db2.tb_a; -- 跨库搬
| ALTER TABLE tb_a RENAME TO tb_b; -- 只能一张、同库
① 教材原码,本沙箱不执行
写法 一次改几张 能跨库 是不是原子
-------------------------------------------------------------------------
`RENAME TABLE` 任意多张 能 能(多张一起成或一起败)
`ALTER TABLE ... RENAME TO` 只能一张 不能 单张当然原子
**第三列是很多人不知道的:`RENAME TABLE a TO b, c TO d` 是原子的。**
**要么两张都改成,要么一张都不动,没有「改了一半」的中间态。**
**这一条在发版时很有用:新表和旧表互换名字,业务方看不到中间空窗。**
**所以「先建一张新表,再换名」是标准的在线切换手法。**
二、改名之后,数据、索引、视图会怎样
--------------------------------------------------------------------
mysql> ALTER TABLE tb_old RENAME TO tb_new
**数据和索引都跟着走:新名字下 3 行,索引是 i_name。**
**因为改名只动数据字典里那一个字符串,数据页和索引页一个字节都不用挪。**
**所以再大的表改名也是秒回——这是它和「改字段类型」最大的区别。**
**接下来试那个视图。按常识它应该坏掉,因为视图里存的是 `FROM tb_old` 这句文本。**
**我查了一次,结果和预期相反:**
动作 结果
-------------------------------------
SELECT * FROM v_old(改名后) 还能查
**居然还能查。因为 SQLite 从 3.25 起会在改名时顺手把视图定义里的表名一起改掉。**
**这是它的体贴,不是所有库的通用规律。**
**MySQL 不这么做。视图里那句 `FROM tb_old` 就是一段文本,表改名之后它照样指着旧名字。**
**所以这条要在真 MySQL 上验,结论是「会坏」。**
**实践上的动作是:改名前先查一遍谁引用了它。**
**MySQL 里可以查 `information_schema.VIEWS` 的 `VIEW_DEFINITION`,把表名搜一遍。**
三、一次改两张,中途失败会怎样
--------------------------------------------------------------------
**我故意让第二句失败:`tb_d` 已经存在了。**
**第一句是把 `tb_a` 改成 `tb_b`,它是合法的;第二句要撞上已有的表。**
时刻 表有哪些
--------------------------------------------------------------------------
动手之前 tb_a、tb_c、tb_d
第二句报错 there is already another table or index with this name: tb_d
事务回滚之后 tb_a、tb_c、tb_d
**结果是没有出现 `tb_b`,第一句也跟着退了。**
**这就是原子:两句在一个事务里,要么全成要么全不成。**
**真 MySQL 的 `RENAME TABLE a TO b, c TO d` 也是这样,它内部就是一个操作,不是两条语句。**
**这一点在切换发布时很关键:不会出现「一半改了、一半没改」的中间态。**
四、跨库搬表
--------------------------------------------------------------------
**沙箱里没有「一个服务器多个库」的概念,用 `ATTACH` 挂第二个文件来模拟。**
**真 MySQL 一句 `RENAME TABLE db1.tb TO db2.tb` 就完事,这里要三步:**
步骤 结果
-----------------------------------------------------
在 db2 建同名表结构 成功
把数据拷过去 db2 里有 3 行
删掉 db1 的源表 db1 里还剩 0 张名叫 tb_move 的表
**三步不是原子的,中间任何一步失败都会留下两张表或者一份数据。**
**真 MySQL 那一句是原子的,这也是「能一句就别拆三步」的典型例子。**
**还有一个细节:跨库搬表在同一台服务器里只是改字典,不搬数据。**
**但如果两个库在同一个 InnoDB 表空间下,它连文件都不用动。**
第一段:改名之后,新名字下 3 行,索引还是 i_name。
数据和索引都跟着走,因为改名只动字典里那一个字符串。
第二段是这一课最值得记的地方。按常识视图应该坏掉,因为它存的是 FROM tb_old 这句文本。
我查了一次,它居然还能查——因为 SQLite 从 3.25 起会在改名时顺手把视图定义里的表名一起改掉。
这是 SQLite 的体贴,不是通用规律。
MySQL 不这么做:视图里那句 FROM tb_old 就是一段文本,改完名它照样指着旧名字。
所以这条要在真 MySQL 上验,结论是「会坏」。
第三段:第二句故意撞上已存在的 tb_d,报错之后回滚,tb_b 没出现。
表清单回到 tb_a、tb_c、tb_d,第一句也跟着退了。
这就是原子——真 MySQL 的 RENAME TABLE a TO b, c TO d 内部就是一个操作。
第四段跨库搬表要三步:建结构、拷数据、删源表。
db2 里有 3 行,db1 里剩 0 张。
三步不是原子的,真 MySQL 一句 RENAME TABLE db1.t TO db2.t 才是。
30 秒实验
- 把改名那一步改成
RENAME TABLE多张互换的写法,想想为什么中间不会有空窗。 因为两句合成一个操作,外面看不到「两个名字都不存在」的那一瞬间。 - 建一个存储过程,里面写
SELECT * FROM tb_old,再改名,然后调用它。 会报错。存储过程里的表名也是文本。 这一条在真 MySQL 上一定成立,SQLite 不支持存储过程所以验不了。 - 把
tb_a改名成TB_A(只改大小写),看会不会成功。 在 Windows 上可能报「已存在」,因为文件名不区分大小写。 Linux 上要看lower_case_table_names参数。
常见误区
- 「改名之后视图会自动跟着改。」 MySQL 不会。视图里存的是文本,改名之后它就指着旧名字。 SQLite 3.25 之后会自动改,但那是它的特性,不能当通用规律。
- 「一次改两张就是执行两条语句。」
RENAME TABLE a TO b, c TO d是一个操作,原子。 拆成两条单独执行才不是原子的,中间会出现两个名字都空着的瞬间。 - 「跨库搬表会把数据复制一遍。」 同一台服务器上只是改字典,不搬数据。 跨服务器才需要导出再导入,那是另一件事。
- 「改名是小事,不用通知人。」 应用程序里的 SQL、报表脚本、定时任务都可能硬编码了表名。 数据库这边秒回,业务那边可能整晚报错。
自测三问(点开对答案)
1. `RENAME TABLE` 和 `ALTER TABLE ... RENAME TO` 差在哪? —— **前者能一次改多张、能跨库,多张之间是原子的。** **后者只能改一张、只能在同一个库里。** 2. 为什么改名再大的表也是秒回? —— **因为表名只存在数据字典里,改名就是换个字符串,数据页和索引页一个字节都不动。** 3. 改名之后哪一类对象最容易悄悄坏掉? —— **视图和存储过程。它们存的是表名文本,不会跟着改名更新,而且不一定当场报错。**6.4 到这儿结束。下一篇 6.5 讲复制表——建新表再换名这个手法,前半截就是「复制表」。
- ☐ (本篇完,下一篇 6.5 复制表)