建库只有一条语句,但写法有四种,差别全在「库已存在」那一刻。
脚本里必须写 IF NOT EXISTS,否则跑第二遍就停在那儿。
建库几乎不花时间:它只在磁盘上建一个目录,不预分配空间。
上一篇认清了库是什么,这一篇开始建。 它是本章第一段真语法,后面查看、选择、修改、删除都是围着它转。 建库时定的字符集,会一路继承到后面建的表,所以这一步别糊弄。 和第 5 章的关系:库建错了字符集,第 5 章建表时就只能一个个显式指定,很麻烦。
一、四种写法,一个比一个稳
教材给了四种,其实是一条语句的四种变体。
| 写法 | 库已存在时 | 什么时候用 |
|---|---|---|
| CREATE DATABASE 库名 | 报错 1007 | 一次性手工建 |
| CREATE SCHEMA 库名 | 报错 1007 | 同上,等价写法 |
| CREATE DATABASE IF NOT EXISTS 库名 | 静默跳过 | 初始化脚本必须这么写 |
| CREATE DATABASE 库名 CHARACTER SET ... | 报错 1007 | 显式指定字符集 |
第三种的「静默」是关键:它不报错,也不提示,只是什么都不做。 所以脚本跑完你看不出它到底建了还是跳过——要确认得再查一次。
二、为什么要幂等
「幂等」是这么一件事:同一份脚本跑一遍和跑十遍,结果一样。 初始化脚本必须幂等,因为它一定会被重复执行。
| 场景 | 不幂等会怎样 |
|---|---|
| 新机器初始化 | 第一次成功,第二次停在 1007 |
| 容器重启自动执行 | 每次重启都失败,日志全是红的 |
| 同事拿到脚本重跑 | 以为脚本坏了 |
这条规矩不只适用于建库,建表、插初始数据都一样。
CREATE TABLE IF NOT EXISTS、INSERT ... ON DUPLICATE KEY UPDATE,都是同一个思路。
三、建库时定字符集,别等以后改
CREATE DATABASE 库名 CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
这一句里两个参数:字符集管「能存什么」,校对规则管「怎么比」。
8.0 起默认就是 utf8mb4,所以不写也对。
但生产上建议显式写——默认值会随版本变,写下来才不会被升级改掉。
为什么不写会出事?举个具体的:5.7 时代默认是 utf8(三字节)。
一个 5.7 建的库升到 8.0,字符集不会跟着变,数据文件里就是三字节的东西。
之后用户存一个 emoji,报 Incorrect string value。
四、建库这一步在磁盘上做了什么
几乎什么都没做。 它只在数据目录下建一个同名子目录,写一条元数据记录。
| 动作 | 花多少时间 | 占多少空间 |
|---|---|---|
| 建库 | 毫秒级 | 几十字节的元数据 |
| 建表 | 毫秒级 | 一个空的表空间文件 |
| 插数据 | 看行数 | 真正开始吃磁盘 |
| 建索引 | 看行数 | 可能和数据一样大 |
所以「库太多会不会拖慢」这个问题,答案是「不会」。 库只是目录,真正影响性能的是表、索引和数据量。 反过来说,「库多」会让人找不到东西。 一个实例上几百个库,谁也说不清哪个是哪个——这是管理问题,不是性能问题。
五、动手 Demo:建库的四种写法,和它们各自的下场
这一课把四种写法都跑一遍,看报错和不报错的分界。 沙箱没有 MySQL,所以造了一个最小模型:一个库一个目录,元数据存在 meta.json 里。
import json, os, shutil
class Catalog(object):
def create(self, name, charset="utf8mb4",
collate="utf8mb4_0900_ai_ci", if_not_exists=False):
if os.path.isdir(self._d(name)):
if if_not_exists:
return "0 行受影响(库已存在,跳过)"
raise ValueError("ERROR 1007 (HY000): Can't create database "
"'%s'; database exists" % name)
os.makedirs(self._d(name))
self._save(name, {"charset": charset, "collate": collate})
return "Query OK, 1 row affected"
cat = Catalog(DATA)
print(cat.create("db_database28")) # 第一次,成功
try:
print(cat.create("db_database28")) # 第二次,1007
except ValueError as e:
print(e)
print(cat.create("db_database28", if_not_exists=True)) # 静默跳过
实跑输出:
--------------------------------------------------------------------
第十八课:建库的四种写法,和它们各自的下场
--------------------------------------------------------------------
**教材 4.2 节给了四种建库写法,差别全在「库已存在」那一刻。**
**这一课把这四种都跑一遍,看报错和不报错的分界。**
一、教材原码:四种写法
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE DATABASE db_database28;
| CREATE SCHEMA db_database28;
| CREATE DATABASE IF NOT EXISTS db_database28;
| CREATE DATABASE db_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
(建库、等价写法、带判断、指定字符集与校对规则)
二、真跑:第二次建同一个库会怎样
--------------------------------------------------------------------
写法 情形 结果
---------------------------------------------------------------------------------------
CREATE DATABASE 第一次 Query OK, 1 row affected
CREATE DATABASE 第二次 ERROR 1007 (HY000): Can't create database 'db_data…
IF NOT EXISTS 库已存在 0 行受影响(库已存在,跳过)
CREATE ... CHARACTER SET 新库 Query OK, 1 row affected
**第二行那个 1007 就是「库已存在」。**
**脚本里不写 `IF NOT EXISTS`,跑第二遍就停在这儿。**
**初始化脚本必须写成第三种。**
**幂等——同样一份脚本跑一遍和跑十遍,结果必须一样。**
三、建库到底在磁盘上做了什么
--------------------------------------------------------------------
库名 落地成什么 占多少字节
-------------------------------------
db_database28 一个目录 55
db_shop 一个目录 55
**MySQL 的库就是一个目录(8.0 在 `datadir` 下,名字就是库名)。**
**所以「建库」这一步几乎不花时间,也不预分配空间。**
**这一点决定了后面一件事:库本身不占空间,占空间的是表和索引。**
注意那个 if_not_exists 参数:它把「报错」和「跳过」做成了一个开关。
MySQL 内部也是这么实现的——同一段代码,多一个判断。
第三行那个 1007 是 MySQL 的错误码,真机上措辞一模一样:
Can't create database 'xxx'; database exists。
记住这个码,比记住整句话有用。
第四行「0 行受影响」看着像失败,其实成功了——它只是什么都没做。
所以脚本跑完想确认结果,得再查一次库列表,不能只看这条语句的返回。
最后那张表里,两个库都是 55 字节,就是 meta.json 的大小。
这个数说明「建库几乎不占空间」,也说明「库大多不是问题」。
30 秒实验
- 把 Demo 里第一次那句也加上
if_not_exists=True,再跑一遍。 两次都返回「0 行受影响」和「Query OK」的混合结果, 这就是幂等脚本的样子:不管从什么状态开始,最后都是同一个状态。 - 把
CREATE DATABASE换成CREATE SCHEMA,跑一遍。结果完全一样。 SQL 标准里叫 schema,MySQL 两个词都认。 - 想一想:为什么不在
CREATE DATABASE时就把大小定下来? 因为库的大小取决于里面的表和索引,事先定不下来。 能定的是「这个库最多用多少空间」,那是配额,是另一套机制。
常见误区
- 「脚本里有
CREATE DATABASE就够了,不用加 IF NOT EXISTS。」 跑第二遍就停在 1007。 初始化脚本一定会被重复执行,必须幂等。 - 「建库不写字符集,到时候再改。」
改库只改「以后新建的表」,老表一张都不动。
建库时那句
CHARACTER SET utf8mb4是最便宜的一次保险。 - 「库建多了会拖慢 MySQL。」 不会。库就是目录,几乎不占空间也不耗性能。 真正拖慢的是表、索引和数据量;库多只是让人找不到东西。
自测三问(点开对答案)
1. 第二次执行 `CREATE DATABASE db_database28;` 会怎样? —— **报 1007:`database exists`。** **加上 `IF NOT EXISTS` 就变成静默跳过。** 2. 为什么初始化脚本必须幂等? —— **因为它一定会被重复执行**:新机器跑一次、容器重启跑一次、同事拿到再跑一次。 **不幂等的脚本,第二次就一定失败。** 3. 建库时最该显式写出来的两个参数是什么? —— **字符集(CHARACTER SET)和校对规则(COLLATE)。** **不写会跟着版本默认值走,升级时可能悄悄变。**下一篇讲怎么看库:看列表、看定义、看占地,三种视角对应三条完全不同的语句。
- ☐ (本篇完,下一篇 4.3 查看数据库)