建库只需要一条语句,但「建之前想清楚字符集」这件事,事后改代价很大。 改库只改得动默认值,改不动已有的表。 删库没有回收站,也没有确认三次。
上一篇把工具装好了,这一篇开始建东西——从最外层的库开始。 它是第 4 章的地基:第 4 章会把这一篇的建库、查看、选择、修改、删除逐条展开讲语法, 这一篇讲的是「在图形工具里怎么点」。 记住一个顺序:库、表、字段、数据。 这一篇在第二层,下一篇再往下一层。
一、建库:三个写法,一个意思
CREATE DATABASE 库名; 和 CREATE SCHEMA 库名; 完全等价。
SQL 标准里这个词叫 schema,MySQL 两个都认。
第三个写法最实用:CREATE DATABASE IF NOT EXISTS 库名;。
库已经存在时不报错,只是什么都不做。
脚本要跑第二遍的时候,这一句能救命。
| 写法 | 库已存在时 | 什么时候用 |
|---|---|---|
| CREATE DATABASE 库名 | 报错 1007 | 一次性手工建 |
| CREATE SCHEMA 库名 | 报错 1007 | 同上,等价写法 |
| CREATE DATABASE IF NOT EXISTS 库名 | 静默跳过 | 初始化脚本里必须这么写 |
二、建库时最该定的两件事
建库时可以什么都不写,让它用默认字符集。 但生产上建议显式写出来,因为默认值会随版本变。
| 要定的 | 写什么 | 不定的后果 |
|---|---|---|
| 字符集 | CHARACTER SET utf8mb4 | 5.7 默认是三字节的 utf8,emoji 存不进 |
| 校对规则 | COLLATE utf8mb4_0900_ai_ci | 排序和大小写比较按默认走,可能不合业务 |
一句话:新建库一律 utf8mb4。
8.0 起默认就是它,但 5.7 建的库升上来还是旧的三字节 utf8,得手动改。
三、改库:只改得动「以后」
ALTER DATABASE 库名 CHARACTER SET utf8mb4; 改的是「这个库以后新建的表用什么字符集」。
已经存在的表,一动不动。
这一点是本章最容易误解的地方,Demo 里有实例:
改完库的字符集,老表的字符集还是老的,得一张张 ALTERTABLE ... CONVERT TO。
为什么不自动改?
因为改字符集要重写每一行数据,几百 G 的表能跑一整晚,还全程锁表。
数据库不敢替你决定这件事。
四、删库:一条语句,一个终点
DROP DATABASE 库名; 执行完,库里的表、数据、索引、视图,全没。
没有回收站,没有「确认三次」,没有撤销。
| 你想的 | 实际的 |
|---|---|
| 删错了能恢复 | 只能靠之前的备份 |
| 先删表再删库比较安全 | 一样,都不可逆 |
| 工具会拦一下 | 工具只弹一次确认框,你顺手就点了 |
所以真正的保护在流程里:删之前先导出一份。 3.5 节的导出功能,最大的用处不是搬家,是「删之前留个底」。
五、动手 Demo:建库、改库、删库,在图形工具里点三下
教材原码先贴上,再用 SQLite 演等价的动作。 差别在于:MySQL 的库是服务端里的一个命名空间,SQLite 的库就是一个文件。 五段:原码、建三个库、一个连接挂多个库、删库、以及工具在这一步帮了什么。
import sqlite3, os
# 「建库」在 SQLite 里就是建一个文件
for name in ("db_shop", "db_blog", "db_test"):
p = os.path.join(WORK, name + ".db")
c = sqlite3.connect(p)
c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
c.execute("INSERT INTO t VALUES (1, ?)", (name,))
c.commit(); c.close()
print(name, os.path.getsize(p), "字节")
# 「一个连接挂多个库」在 SQLite 里是 ATTACH
conn = sqlite3.connect(os.path.join(WORK, "main.db"))
conn.execute("ATTACH DATABASE ? AS db_shop",
(os.path.join(WORK, "db_shop.db"),))
print([r[1] for r in conn.execute("PRAGMA database_list")])
print(conn.execute("SELECT * FROM db_shop.t").fetchall())
# 「删库」就是删文件
os.remove(os.path.join(WORK, "db_test.db"))
print("删完还在吗:", os.path.exists(os.path.join(WORK, "db_test.db")))
实跑输出:
--------------------------------------------------------------------
第十课:建库、改库、删库,在图形工具里点三下
--------------------------------------------------------------------
**教材 3.2 节讲的是在 phpMyAdmin 里建库、改库、删库。**
**点的那三下,背后是三条 SQL。**
**沙箱里没有 MySQL,所以先贴教材原码,再用 SQLite 演等价的动作。**
一、教材原码:四条语句
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE DATABASE db_database28;
| CREATE SCHEMA db_database28; -- 和 CREATE DATABASE 等价
| CREATE DATABASE IF NOT EXISTS db_database28; -- 不存在才建,不报错
| ALTER DATABASE db_database28 CHARACTER SET utf8mb4;
| DROP DATABASE db_database28;
(建库、建库的另一种写法、带判断的建库、改库字符集、删库)
二、SQLite 里「一个库」就是一个文件
--------------------------------------------------------------------
**MySQL 的库是服务端里的一个命名空间,SQLite 的库就是一个 `.db` 文件。**
**这个差别决定了后面所有操作长得不一样。**
建出来的库 落地成一个文件 文件字节数
--------------------------------------
db_shop db_shop.db 8192
db_blog db_blog.db 8192
db_test db_test.db 8192
三、一个连接里挂多个库:ATTACH
--------------------------------------------------------------------
ATTACH DATABASE 'db_shop.db' AS db_shop
ATTACH DATABASE 'db_blog.db' AS db_blog
**一个连接上现在挂着 3 个库:main、db_shop、db_blog。**
SELECT *
FROM db_shop.t
mysql> SELECT * FROM db_shop.t
id v
----------
1 db_shop
1 row in set
SELECT *
FROM db_blog.t
mysql> SELECT * FROM db_blog.t
id v
----------
1 db_blog
1 row in set
四、删库:一条命令,一个文件
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| DROP DATABASE db_test;
(MySQL 里删完,库里的表跟着全没)
**删之前文件在不在:True;删之后:False。**
**注意:没有「回收站」,也没有「确认三次」。**
**生产环境上 `DROP DATABASE` 敲错一个字母,就是一次事故。**
五、图形工具在这一步帮了什么
--------------------------------------------------------------------
你担心的 工具能做 工具做不到
----------------------------------------------------
库名打错 下拉框选,不给你手打 选错行还是删错
删之前忘了看 弹一个「确认删除」 你顺手点了「是」
删完想找回来 没有这个功能 只能靠备份
**所以真正的保护不在工具里,在流程里:删之前先导出一份。**
第一段的三个库,每个都是一份 8192 字节的文件。
这个数不是巧合,它是 SQLite 的页大小×页数,空库一建出来就这么大。
第二段那个 ATTACH 是 SQLite 特有的动作:一个连接可以同时挂多个库文件。
MySQL 不用这一手——它天生就是一个服务端管很多库,USE 库名 就是切换。
第三段一行代码就删了库。
这就是这一节要你记住的:删库在任何数据库上都是「一条语句的事」。
代价全在事后。
最后那张表列了工具能帮和帮不了的。
最扎心的是第三行:它只能弹出一次确认框,而人是会顺手点「是」的。
30 秒实验
- 把
CREATE DATABASE改成CREATE DATABASE IF NOT EXISTS,连跑两遍。 第一遍建,第二遍静默跳过。 这就是初始化脚本里必须这么写的原因。 - 在 Demo 里再 ATTACH 一个库,
然后用
SELECT * FROM db_blog.t UNION ALL SELECT * FROM db_shop.t把两个库的数据拼起来。 能拼,但生产上没人这么干——跨库查询一多,就该考虑合库或者分片了。 - 想一想:为什么数据库不做一个「删库回收站」? 因为几百 G 的数据放进回收站,等于占着两份空间,而真出事的概率很低。 成本恒定、收益偶发,工程上就不做。
常见误区
- 「改了库的字符集,整库就都变成 utf8mb4 了。」
不会。它只改「以后新建的表」的默认值。
老表得一张张
CONVERT TO CHARACTER SET,而且会重写数据、锁表。 - 「库名随便起,反正可以改。」 库名能改,但要 RENAME,而且改完所有连接串、脚本、备份计划都得跟着改。 起名的时候就按规范来,别指望事后改。
- 「删库之前先删表,多一道保险。」
没有这道保险。
DROP TABLE和DROP DATABASE一样不可逆。 真正的保险是备份,而且是验证过能恢复的备份。
自测三问(点开对答案)
1. `CREATE DATABASE` 和 `CREATE SCHEMA` 有区别吗? —— **没有,完全等价。** **SQL 标准里叫 schema,MySQL 两个词都认。** 2. `ALTER DATABASE ... CHARACTER SET` 改的是什么? —— **改的是「以后新建的表」的默认字符集。** **已有表不动,要改得一张张 `CONVERT TO`。** 3. 删库之前最该做的一件事是什么? —— **导出一份,并且确认这份能导回去。** **没验证过的备份等于没有备份。**下一篇往下一层:库里的表怎么建、怎么改、怎么删。
重点是一条 ALTER 能干什么、TRUNCATE 和 DROP 差在哪。
- ☐ (本篇完,下一篇 3.3 管理数据表)