ALTER DATABASE 只能改两样:默认字符集和默认校对规则。
它改的是「以后新建的表」,已有的表一张都不动。
要改老表得 CONVERT TO,那一步会重写数据、锁表,代价按小时算。
上一篇选库,这一篇改库。 这一篇的价值不在语法,在「只改以后」这四个字。 很多人以为改完库就万事大吉,结果一个月后发现老表还是老字符集。 和第 5 章的关系:改完库字符集,第 5 章新建的表就继承新默认值。
一、能改的就两样
ALTER DATABASE 库名 CHARACTER SET 字符集 COLLATE 校对规则;
两个参数可以只给一个,也可以都给。
| 想改 | 语句 | 生效范围 |
|---|---|---|
| 默认字符集 | ALTER DATABASE 库名 CHARACTER SET utf8mb4 | 以后新建的表 |
| 默认校对规则 | ALTER DATABASE 库名 COLLATE utf8mb4_bin | 以后新建的表 |
| 两个一起 | ALTER DATABASE 库名 CHARACTER SET ... COLLATE ... | 以后新建的表 |
注意三行的「生效范围」写的是同一句话。
这不是我抄懒了,是它的全部含义就这一句。
库名、库的创建时间、库的属主,这些都不能用 ALTER DATABASE 改。
库名要改得用 RENAME DATABASE(老版本有,8.0 已经删了)。
所以库名基本等于「一次定终身」——建的时候想清楚。
二、「只改以后」到底是什么意思
表在创建时,会把「当时的库默认值」抄一份记在自己的定义里。 所以库里改了默认值,已经存在的表定义不变。
| 对象 | 字符集从哪来 |
|---|---|
| 建表时没写 CHARSET | 抄当时的库默认值 |
| 建表时写了 CHARSET | 用它写的那个 |
| 改库默认值之后新建的表 | 抄新的默认值 |
| 改库默认值之前建的表 | 还是老的,一动不动 |
为什么这么设计?因为改字符集要重写每一行数据。 几百 G 的表能跑一整晚,全程锁表——数据库不敢替你决定这件事。 所以「改库」和「改表」是两个动作,必须分开做。 而且改表的顺序有讲究:先小表后大表,先低峰后高峰。
三、要改老表,只能一张张来
ALTER TABLE 表 CONVERT TO CHARACTER SET utf8mb4;
这一条会把表里所有字符列一起改掉,并且重写数据。
| 改什么 | 代价 | 注意 |
|---|---|---|
| 库默认值 | 瞬间 | 只改元数据 |
| 一张小表 | 几秒 | 期间会锁表 |
| 一张大表 | 几十分钟到几小时 | 要挑低峰,要留磁盘 |
| 几百张表 | 按加起来算 | 得写脚本批量跑,还要能续跑 |
第四行是真干活的形态:没人手动一张张敲。
得先查出所有表的字符集,生成一批 ALTER 语句,再一条条执行、记日志、失败重试。
还有一个坑:CONVERT TO 会重建索引。
所以它占的临时空间大约是「数据 + 索引」的总和,磁盘不够会中途失败。
四、改之前先摸清有多少要改
一条查询就能列出「哪些表还是老字符集」。
| 查什么 | 从哪查 |
|---|---|
| 每张表的字符集 | information_schema.TABLES 的 TABLE_COLLATION |
| 每个列的字符集 | information_schema.COLUMNS 的 COLLATION_NAME |
| 库的默认值 | information_schema.SCHEMATA 的 DEFAULT_CHARACTER_SET_NAME |
三处都要看。 表级改了、列级没改,join 时照样隐式转换;库级改了、表级没改,等于没改。 规范里那句「同库同表同列字符集必须一致」,说的就是这三处要对齐。
五、动手 Demo:改库只改「以后」,这是它最反直觉的地方
这一课把「改之前 / 改之后 / 老表有没有跟着变」三件事做实。 模型里每个库有一份 meta.json 记字符集,改库就是改这份元数据。
cat = Catalog(DATA)
cat.create("db_database28", charset="utf8", collate="utf8_general_ci")
cat.use("db_database28")
c = cat.conn()
c.execute("CREATE TABLE tb_old (id INTEGER PRIMARY KEY, name TEXT)")
# 改之前
print(cat.show_create("db_database28"))
# 改:字符集和校对规则一起换
old, new = cat.alter("db_database28",
charset="utf8mb4",
collate="utf8mb4_0900_ai_ci")
print(old, "->", new)
# 改之后
print(cat.show_create("db_database28"))
# 老表动没动?再建一张新表对比
c.execute("CREATE TABLE tb_new (id INTEGER PRIMARY KEY, name TEXT)")
实跑输出:
--------------------------------------------------------------------
第二十一课:改库只改「以后」,这是它最反直觉的地方
--------------------------------------------------------------------
**教材 4.5 节讲修改数据库,一条 `ALTER DATABASE`。**
**它只能改两样:默认字符集和默认校对规则。**
**最反直觉的是「只改以后」:已有的表一张都不动。**
**这一课把这件事做实。**
一、教材原码
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| ALTER DATABASE db_database28 CHARACTER SET utf8mb4;
| ALTER DATABASE db_database28 COLLATE utf8mb4_0900_ai_ci;
| ALTER DATABASE db_database28 CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
(改字符集、改校对规则、两个一起改)
二、真跑:改之前和改之后
--------------------------------------------------------------------
**改之前:**
CREATE DATABASE `db_database28` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE
utf8_general_ci */
**改之后:**
CREATE DATABASE `db_database28` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE
utf8mb4_0900_ai_ci */
项 改之前 改之后
---------------------------------------------
字符集 utf8 utf8mb4
校对规则 utf8_general_ci utf8mb4_0900_ai_ci
三、老表动没动
--------------------------------------------------------------------
表 建的时间 它继承到的字符集
-------------------------------------------
tb_old 改之前 utf8(没跟着变)
tb_new 改之后 utf8mb4(继承了新默认值)
**这就是「只改以后」的实样:老表还是老字符集。**
**要改老表,得一张张 `ALTER TABLE 表 CONVERT TO CHARACTER SET utf8mb4`。**
**为什么数据库不自动改?因为那要重写每一行数据。**
**几百 G 的表能跑一整晚,全程锁表,数据库不敢替你决定。**
四、改字符集的三档代价
--------------------------------------------------------------------
改什么 语句 代价
------------------------------------------------------------------------
库默认值 ALTER DATABASE ... CHARACTER SET 瞬间,只改元数据
表(含数据) ALTER TABLE ... CONVERT TO ... 重写整表,大表按小时算
某一列 ALTER TABLE ... MODIFY 列 新字符集 重写这一列,同样慢
**第三档最容易被顺手做:只改一个字段看着聪明,其实照样要重写。**
**而且改完这一列和别的列字符集不同,join 时又要隐式转换。**
第一段那个库是故意用 utf8 建的——模拟一个 5.7 时代留下来的老库。
这一节讲的所有麻烦,都是从这种库开始的。
alter 返回了「改之前」和「改之后」两组值,这里打出来是为了让你看清只有这两个字段变了。
库名、创建时间、里面的表,一个都没被碰。
第三段那两张表是关键:tb_old 是改之前建的,tb_new 是改之后建的。
它们继承到的默认字符集不一样——这就是「只改以后」的实样。
最后那张三档代价表里,第三档最容易被顺手做。
「只改一个字段」看着聪明,其实照样重写。
改完这一列和别的列不一致,join 时又要隐式转换。
30 秒实验
- 把 Demo 里
cat.alter那一句的charset参数去掉,只改collate。 字符集不变,校对规则变了。 这说明两个参数是独立的,可以只改一个。 - 把
utf8mb4_0900_ai_ci换成utf8mb4_bin,想想哪些查询结果会变。 所有=、IN、DISTINCT、GROUP BY的大小写敏感性都会变。 - 想一想:一亿行的表要转字符集,怎么做得更稳? 分三次:先加一个影子列同步写、再分批回填、最后切换。 一次做完的风险是「跑了一晚上,中途失败,回滚又要一晚」。
常见误区
- 「改完库的字符集,整库就升级了。」
只改「以后新建的表」。
老表得一张张
CONVERT TO,而且会重写数据、锁表。 - 「
CONVERT TO只是改个定义,很快。」 它要重写每一行、重建索引,占的临时空间约等于「数据加索引」。 磁盘不够会中途失败。 - 「库名起错了,改一下就行。」
8.0 已经没有
RENAME DATABASE了。 能做的只有「建新库、导数据、删旧库」,这也是一次停机。
自测三问(点开对答案)
1. `ALTER DATABASE ... CHARACTER SET` 的生效范围是什么? —— **只对以后新建的表生效。** **已有的表定义不变,要改变得 `ALTER TABLE ... CONVERT TO`。** 2. 为什么数据库不自动把老表一起改掉? —— **因为改字符集要重写每一行数据,大表能跑一整晚,全程锁表。** **这个决定不该由数据库替你做。** 3. 改字符集之前要摸清哪三处? —— **库默认值、每张表的字符集、每个列的字符集。** **三处不对齐,join 时照样隐式转换、索引失效。**下一篇是本章最后一节,也是最危险的一节:删库。 删之前量什么、删错了怎么救、以及比备份更该做的三件事。
- ☐ (本篇完,下一篇 4.6 删除数据库)