CREATE DATABASE 的三组参数:SIZE 起步给多少、MAXSIZE 最多长到哪、
FILEGROWTH 一次长多少。
长到 2 GB:按 10% 长要 56 次、最后一次长 189 MB;按 64 MB 固定长要 32 次、每次都是 64 MB。
DROP DATABASE 没有回收站,它真删文件;库正在被连的时候还删不掉。
4.1 讲了库是由哪些文件组成的,4.2 讲了名字怎么起,这一节动手:把库建出来、改参数、再删掉。
教材 4.3 分三小节:创建、修改、删除,最后带一个「小结」和「实践与练习」。
三小节里最容易吃亏的是「修改」——改文件路径那条,SQL Server 不会替你搬文件。
演示里把教材那段 CREATE DATABASE 原码贴出来(SQL Server 专用,本沙箱不跑),
再用 SQLite 做等价动作:建文件、定页大小、算增长次数、删文件。
增长那一段算出来的数是真算的,不是抄的。
一、建库:三组参数决定它以后怎么长
教材给的建库语句是这个样子:
CREATE DATABASE db_shop
ON PRIMARY
(
NAME = 'db_shop_data',
FILENAME = 'D:\SQLData\db_shop.mdf',
SIZE = 10MB,
MAXSIZE = 500MB,
FILEGROWTH = 10%
)
LOG ON
(
NAME = 'db_shop_log',
FILENAME = 'D:\SQLData\db_shop_log.ldf',
SIZE = 5MB,
FILEGROWTH = 1MB
)
这段语法是 SQL Server 专用的,本沙箱没有实例,跑不了。 但它的三组参数值得逐个看:
| 参数 | 管什么 | 写错的后果 |
|---|---|---|
| SIZE | 一建出来就占多大 | 太小,运行中频繁增长 |
| MAXSIZE | 最多能长到多大 | 不写就是不限,可能吃满盘 |
| FILEGROWTH | 一次长多少 | 太小,增长次数太多 |
SIZE 不是上限,是起步。 建库时它就直接占掉这么多——演示里 SQLite 建的库只有 32 KB,
而 SQL Server 会一上来就占 10 MB,
这是两种策略:SQLite「用多少占多少」,SQL Server「先占一块地盘」。
先占地的理由是不想让文件在运行中频繁增长。 每次增长都要暂停写入,一秒钟的停顿用户就能感觉到。
所以宁可一开始多占点。
MAXSIZE 建议写。 不写就是不限,一个写错的循环能把整块磁盘吃满——磁盘满了之后,日志写不进去,
整个实例都会拒绝服务,影响的不止这一个库。
二、增长方式:10% 还是固定值
演示里算了一遍:从 10 MB 长到 2 GB,两种方式各要长几次。
| 增长方式 | 要长几次 | 最后一次长多少 |
|---|---|---|
| 每次长 10% | 56 次 | 189 MB |
| 每次长 64 MB | 32 次 | 64 MB |
按百分比长,次数看着不多,但最后几次一次要长一百多 MB。那一下的停顿很明显,
而且越到后面越长——这叫「增长惩罚」。
按固定值长,次数多,但每次停顿都一样短。 生产库一般用固定值,比如 64 MB 或 256 MB。
教材给的是 10% 和 1 MB 两个值,其中 1 MB 那个偏小。
日志文件的 FILEGROWTH 更要小心。 日志是顺序追加的,
一次长 1 MB 意味着写几 MB 数据就要长一次。建议日志一次给足,或者按 256 MB 固定增长。
还有一招比调参数更有效:预先给够。 知道这个库一年会长到 20 GB,
建的时候就把 SIZE 设成 20 GB。磁盘空间是一次性分配的,但增长停顿一次都不会有。
三、改库:能改的比想的多
| 要改什么 | 写法 | 要不要停服务 |
|---|---|---|
| 加数据文件 | ADD FILE | 不用 |
| 改增长方式 | MODIFY FILE | 不用 |
| 改排序规则 | COLLATE | 要独占,得踢人 |
| 改文件路径 | MODIFY FILE | 要离线 |
| 改恢复模式 | SET RECOVERY | 不用 |
「改文件路径」这一条最容易踩。 很多人以为改完参数 SQL Server 就把文件搬过去了——
不会。它只改「以后去哪儿找」,文件还在老地方。
正确顺序是四步:
把库设成离线 → 手工把文件搬过去 → 改路径参数 → 再把库设成在线。少一步就报「找不到文件」,
而且这时候库已经离线了,只能再改回来。
「改排序规则」要独占访问。 也就是所有连接都得断掉,否则报「数据库正在使用」。
生产库上干这件事得挑时间窗口,因为它会重建所有索引——大库可能要几十分钟。
「改恢复模式」不用停。 从完整切到简单,日志会在下一次检查点时被截断,
.ldf 会突然变小——这个操作经常被用来「清理日志文件」,比 SHRINKFILE 更合理。
四、删库:没有回收站
DROP DATABASE 做三件事:从 master 里摘掉登记、删数据文件、删日志文件。演示里真删了一个文件,
删之前 32 KB,删之后连文件都不在了。
没有回收站。 要么事先有备份,要么就真没了。
教材在这一节末尾提醒「删除前先备份」,只有一句话,但它是这一节代价最高的一句。
库正在被连接时删不掉。 会报「无法删除正在使用的数据库,因为该数据库当前正在使用」。
解决办法有两个:切到别的库再删,或者先 SET SINGLE_USER WITH ROLLBACK IMMEDIATE 把所有人踢掉。
DROP 和 DELETE 差一个字,后果差一个量级。 DELETE 删行,能回滚;
DROP 删对象,删完连表结构都没了。在正式库上敲 DROP 之前,先看一眼连接的是哪个库。
五、动手 Demo:建一个库、算一遍增长、再删掉
这一段先贴教材的 CREATE DATABASE 原码(本沙箱不执行),再用 SQLite 做等价动作:建文件、量页数、
算两种增长方式的次数、列出能改的参数,最后真删一次。
import os, sqlite3
db = u"db_shop.sqlite"
c = sqlite3.connect(db)
c.execute(u"PRAGMA page_size=4096")
c.execute(u"CREATE TABLE tb_goods (id INTEGER PRIMARY KEY, name TEXT, price REAL)")
c.executemany(u"INSERT INTO tb_goods (name, price) VALUES (?,?)",
[(u"商品 %d" % i, i * 1.5) for i in range(1, 1001)])
c.commit()
c.execute(u"PRAGMA wal_checkpoint(FULL)")
pages = c.execute(u"PRAGMA page_count").fetchone()[0]
ps = c.execute(u"PRAGMA page_size").fetchone()[0]
print(u"%d 页 x %d 字节 = %d 字节" % (pages, ps, pages * ps))
# 两种增长方式,各要长几次才到 2 GB
for label, mode in ((u"10%", u"pct"), (u"64MB", u"fix")):
size, n = 10.0, 0
while size < 2048 and n < 400:
grow = size * 0.10 if mode == u"pct" else 64.0
size = min(size + grow, 2048)
n += 1
print(u"%s:%d 次,最后一次长 %.0f MB" % (label, n, grow))
# 删库:真删文件
before = os.path.getsize(db)
c.close()
os.remove(db)
print(u"删前 %d 字节,删后文件还在吗:%s"
% (before, os.path.exists(db)))
实跑输出:
--------------------------------------------------------------------
建库、改库、删库
--------------------------------------------------------------------
① 教材原码:CREATE DATABASE
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE DATABASE db_shop
| ON PRIMARY
| (
| NAME = 'db_shop_data',
| FILENAME = 'D:\SQLData\db_shop.mdf',
| SIZE = 10MB,
| MAXSIZE = 500MB,
| FILEGROWTH = 10%
| )
| LOG ON
| (
| NAME = 'db_shop_log',
| FILENAME = 'D:\SQLData\db_shop_log.ldf',
| SIZE = 5MB,
| FILEGROWTH = 1MB
| );
这段是 SQL Server 专用语法,本沙箱没有实例,跑不了。
能跑的是它的等价动作:建一个文件、定初始大小、定增长方式。
建库:db_shop.sqlite
页大小 4096 字节,共 8 页,32768 字节
文件实际 32768 字节
注意这里只有 32 KB,而教材那个 SIZE = 10MB 是「一建出来就占 10MB」。
SQLite 是「用多少占多少」,SQL Server 是「先占一块地盘」——两边策略不同。
先占地的理由是不想让文件在运行中频繁增长:每次增长都要暂停写入。
所以 SQL Server 那个 SIZE 参数不是「上限」,是「起步就给你这么多」。
② 增长参数:10% 还是固定 64MB
--------------------------------------------------------------------
| 增长方式 | 长到 2 GB 要长几次 | 最终大小 | 最后一次长多少 |
|---|---|---:|---:|
| 每次长 10% | 56 | 2048 MB | 189 MB |
| 每次长 64MB | 32 | 2048 MB | 64 MB |
按百分比长,次数看着少,但最后几次一次要长一百多 MB,那一下的停顿很明显;
按固定 64MB 长,次数多,每次停顿都一样短。生产库一般用固定值。
教材给的是 10% 和 1MB 两个值。1MB 那个偏小,日志会频繁增长——
日志文件建议一次给足,或者按 256MB 固定增长。
③ 改库:能改的比想象的多
--------------------------------------------------------------------
| 要改什么 | 写法 | 要不要停服务 |
|---|---|---|
| 加数据文件 | ADD FILE | 不用 |
| 改增长方式 | MODIFY FILE | 不用 |
| 改排序规则 | COLLATE | 要独占,得踢人 |
| 改文件路径 | MODIFY FILE | 要离线 |
| 改恢复模式 | SET RECOVERY | 不用 |
「改文件路径」这一条最容易踩:不是改完就搬,得先把库设成离线、手工搬文件、再改路径、再上线。
SQL Server 不会替你搬——它只改「以后去哪儿找」,文件还在老地方,上线就报找不到。
④ 删库:DROP 之后就是删了
--------------------------------------------------------------------
DROP 前:db_shop.sqlite 32768 字节
DROP 后:db_shop.sqlite 0 字节(文件都不在了)
SQL Server 的 DROP DATABASE 做三件事:摘掉登记、删数据文件、删日志文件。
没有回收站。要么事先有备份,要么就真没了。
教材在这一节末尾提醒「删除前先备份」,一句话,但代价最高。
还有一条:库正在被连接时删不掉,会报「无法删除正在使用的数据库」。
要先切到别的库、或者用 SINGLE_USER 把人踢掉,才能删。
第一段量出来的是 32 KB,而教材那段写的是 SIZE = 10MB。
这不是矛盾,是策略不同:SQLite 用多少占多少,SQL Server 先占一块地盘。
先占地的理由是不想让文件在运行中频繁增长。
第二段的两个数要放在一起看:56 次对 32 次,189 MB 对 64 MB。按百分比长看着省事,
但最后几次的停顿会越来越长——这叫「增长惩罚」。
第三段是真删。 删之前 32 KB,删之后 os.path.exists 返回 False——不是「移到了回收站」,
是文件没了。SQL Server 的 DROP DATABASE 一样的道理。
这段没有一个字是 SQL Server 的 T-SQL。 它演的是机制:文件怎么来、增长怎么算、删除意味着什么。
真在 SQL Server 上跑,用的是 4.3 开头贴的那段原码。
30 秒实验
- 在 SSMS 里建一个测试库,
SIZE写 5 MB、FILEGROWTH写 1 MB,然后写个循环插几万行, 盯着文件属性的「大小」和「可用空间」两个数。你会看到「大小」一次次跳,这就是增长。 - 把
FILEGROWTH改成 64 MB 再跑一遍,数一下任务管理器里磁盘写入的尖峰少了多少次。 - 想一想:如果一个库的
.ldf有 40 GB,但数据只有 2 GB,最合理的处理是什么? 先切简单恢复模式让日志截断,再SHRINKFILE收回来——直接删文件会让库打不开。
常见误区
- 「改文件路径就是搬文件」。SQL Server 只改「以后去哪儿找」,不搬。 正确顺序是:离线 → 手工搬 → 改路径 → 在线。顺序错了库就起不来,而且它已经离线了, 只能再改回去。
- 「日志文件太大,删掉就行」。删
.ldf会让库直接打不开——SQL Server 认为数据不完整。 正确做法是切简单恢复模式,让日志自己截断,再收空间。 - 「MAXSIZE 不用写,反正磁盘够大」。写错的循环能把盘吃满。
盘满了日志写不进去,整个实例拒绝服务,影响所有库——不只是那一个。
给
MAXSIZE一个合理的上限,是给自己留条后路。
自测三问(点开对答案)
1. `SIZE = 10MB` 是上限还是起步? —— **是起步。** 建库时就直接占掉 10 MB,不是「最多 10 MB」。上限是 `MAXSIZE` 那个参数。 这两个经常被搞混,结果就是「为什么我刚建的库占了 10 MB」。 2. 从 10 MB 长到 2 GB,10% 和 64 MB 各要长几次? —— **10% 要 56 次,最后一次长 189 MB;64 MB 要 32 次,每次都是 64 MB。**次数看着差得不多, 但百分比的最后几次停顿很长——所以生产库用固定值。 3. `.ldf` 涨到 40 GB,能不能直接删? —— **不能,删了库就打不开。** 正确做法是切简单恢复模式让日志截断(或者先做一次日志备份), 再 `SHRINKFILE` 把空间收回来。**直接删文件是最常见的一种「把库弄坏」的方式。**第 4 章到此结束。
下一篇进第 5 章 5.1,讲数据表:
字段类型怎么选、NULL 和空字符串差在哪、IDENTITY 自增列为什么不能自己插值。
- ☐ (本篇完,下一篇 5.1 数据表概述)