五个系统库:master 管登记、model 当模板、msdb 管作业、tempdb 当草稿纸、resource 只读。
三类文件:.mdf 主数据、.ndf 次数据、.ldf 事务日志;
日志记的是「改之前的样子」,不是副本。
文件组的意义只有一条:让冷数据和热数据落在不同的盘上,备份和 IO 都分开。
前三章都在讲「外面」:装在哪、服务怎么起、怎么连。这一章进到里面,看数据库这个对象本身。 教材 4.1 分五小节:基本概念、常用对象、数据库组成、系统数据库、文件与文件组。 五小节里真正天天用得上的是后两节——系统库和文件。 演示里用 SQLite 的文件模型复刻了「数据文件 + 日志文件」和「文件组」两件事, 这两个机制在 SQL Server 里的形状,用 SQLite 的两个文件就能看得清清楚楚。
一、五个系统库,各管一摊
装完 SQL Server,对象资源管理器里会看到五个库,它们不是样本库,是实例自己的账本。
| 库名 | 管什么 | 坏了会怎样 |
|---|---|---|
| master | 登录名、数据库清单、实例配置 | 整个实例起不来 |
| model | 新建库的模板 | 新库会带上它的设置 |
| msdb | 作业、备份历史、维护计划 | 代理跑不了,记录丢 |
| tempdb | 临时表、排序中间结果 | 每次重启重建,满了全卡 |
| resource | 只读,系统对象 | 只能靠打补丁更新 |
master 最特殊,它记着「这个实例上有哪些数据库」。 它坏了, 实例连启动都过不去——所以装完 SQL Server 的第一件事是备份 master, 这句话教材在后面的备份章节还会说一次。 model 是模板,不是样本。 你新建一个库,它是照着 model 抄出来的——model 里的表、视图、排序规则, 新库全都有。有些人往 model 里塞一张公共的日志表,新建的库就都自带这张表。 tempdb 是唯一一个「每次重启清空重建」的库。 所以往里放东西永远是临时的, 连备份都不用做——它本来就不存该留的数据。 resource 是隐藏的。 对象资源管理器里看不见,它存的是系统视图、系统存储过程这些东西,只读, 只能通过打补丁更新。所以「系统存储过程被改了」这种事,正常途径做不到。
二、三类文件,职责完全不同
| 扩展名 | 叫什么 | 几个 | 装什么 |
|---|---|---|---|
| .mdf | 主数据文件 | 1 个,必须有 | 数据页、索引页、系统页 |
| .ndf | 次要数据文件 | 0 到多个 | 分担数据,可放别的盘 |
| .ldf | 事务日志 | 至少 1 个 | 改之前的旧值 |
日志文件不是数据的副本,是「改之前的样子」。 这个区别很关键:数据文件里存的是当前值,
日志里存的是「改成当前值之前它是什么」。回滚靠它、恢复到某个时间点也靠它。
所以日志和数据必须分开放。 放同一块磁盘,磁盘一坏两个一起没,
日志就白记了——它本来是用来在磁盘坏掉之后恢复的。
演示里用 SQLite 的两个文件复刻了这个结构:一个数据文件,一个日志文件。
写操作先落日志,再慢慢合进数据文件——这就是所有关系库的共同套路,SQL Server、MySQL、
PostgreSQL 都一样,区别只在日志留多久、什么时候合并。
SQL Server 的日志不会「提交就删」。 它一直往后追加,直到你做日志备份,或者把恢复模式切成简单。
所以一个只增不减的库,.ldf 会长得比 .mdf 还大——这是新手最常见的「磁盘怎么突然满了」。
三、文件组:为什么要分
文件组是「一组文件的集合」。建表时可以指定 ON 组名,这张表就落在那个组的文件上。
教材只说了一句「文件组便于管理」,实际用起来只有两个理由。
第一个理由:把冷数据放到慢盘上。 三年前的历史订单放在一块大容量机械盘,今年的热数据放在 SSD。
查询热数据不受冷数据拖累,冷数据也不用占 SSD 的空间。
第二个理由:备份可以分开做。 主文件组一周备一次,冷数据那个组一个月备一次。
数据量大的时候,这个差别直接决定备份窗口够不够。
演示里用两个文件复刻了这件事:主文件放 2001 行,次文件放 500 行,
两个文件各自独立增长,互不影响。 SQL Server 里这两个文件就叫两个「文件组」。
别过度设计。 中小型库一个文件组就够了,分多了反而增加管理成本——加文件、调增长、算容量,
每多一个组就多一套。文件组是给「数据量真的很大」准备的。
四、常用对象:一张表说清
| 对象 | 一句话 | 什么时候用 |
|---|---|---|
| 表 Table | 存数据的地方 | 所有业务数据 |
| 视图 View | 存起来的一条查询 | 常查的复杂连接 |
| 索引 Index | 为了查得快而建的目录 | 经常出现在 WHERE 里 |
| 存储过程 Proc | 一段带名字的 SQL | 重复执行的业务逻辑 |
| 触发器 Trigger | 数据一变就自动跑 | 审计、级联更新 |
| 约束 Constraint | 不许写坏数据的规矩 | 主键、外键、非空 |
这六样里,前四样天天用,后两样要慎用。 触发器在 SQL Server 里是「语句级」的, 一次更新一千行只触发一次,但里面要处理一千行的集合——写不好就会拖慢整条语句。 约束比触发器优先。 能用主键、外键、CHECK 解决的问题,不要用触发器——约束是内核执行的,快; 触发器是你写的 SQL,慢而且容易漏。
五、动手 Demo:用两个文件复刻数据文件与日志
这一段先列五个系统库和三类文件,再用 SQLite 建一个库, 把「数据 + 日志」和「文件组」两个机制各演一遍——日志文件会在写入时出现、合并后消失, 第二个文件会独立长大。
import os, sqlite3
db = u"shop.mdf"
c = sqlite3.connect(db)
c.execute(u"PRAGMA journal_mode=DELETE") # 日志提交后就删
c.execute(u"CREATE TABLE tb (id INTEGER PRIMARY KEY, v TEXT)")
c.executemany(u"INSERT INTO tb VALUES (?,?)",
[(i, u"第 %d 行" % i) for i in range(1, 2001)])
c.commit()
print(u"数据文件 %d 字节,日志文件 %d 字节"
% (os.path.getsize(db), os.path.getsize(db + u"-journal")))
c.execute(u"PRAGMA journal_mode=WAL") # 日志攒着不合并
c.execute(u"INSERT INTO tb VALUES (?,?)", (9999, u"再加一行"))
c.commit()
print(u"数据文件 %d,日志文件 %d"
% (os.path.getsize(db), os.path.getsize(db + u"-wal")))
# 文件组:第二个文件,独立长大
c.execute(u"ATTACH DATABASE ? AS hist", (u"hist.ndf",))
c.execute(u"CREATE TABLE hist.tb_old (id INTEGER PRIMARY KEY, v TEXT)")
c.executemany(u"INSERT INTO hist.tb_old VALUES (?,?)",
[(i, u"历史 %d" % i) for i in range(1, 501)])
c.commit()
print(u"次文件 %d 字节" % os.path.getsize(u"hist.ndf"))
实跑输出:
--------------------------------------------------------------------
五个系统库与三类文件
--------------------------------------------------------------------
① 五个系统库,各管一摊
--------------------------------------------------------------------
| 库名 | 管什么 | 坏了会怎样 |
|---|---|---|
| master | 登录名、数据库清单、实例配置 | 整个实例起不来 |
| model | 新建库的模板,所有新库都照它建 | 新建库会带上它的设置 |
| msdb | 作业、备份历史、维护计划 | 代理跑不了,备份没记录 |
| tempdb | 临时表、排序的中间结果 | 每次重启重建,满了全卡 |
| resource | 只读,系统对象和系统存储过程 | 只能靠打补丁更新 |
master 最特殊:它记着「这个实例上有哪些数据库」。它坏了,实例连启动都过不去。
所以装完 SQL Server 的第一件事,是备份 master——这句话教材在后面的备份章节还会说一次。
tempdb 是唯一一个「每次重启都清空重建」的库,所以往里放东西永远是临时的。
② 三类文件,职责不同
--------------------------------------------------------------------
| 扩展名 | 叫什么 | 几个 | 装什么 |
|---|---|---|---|
| .mdf | 主数据文件 | 1 个,必须有 | 数据页、索引页、系统页 |
| .ndf | 次要数据文件 | 0 到多个 | 分担数据,可放不同磁盘 |
| .ldf | 事务日志 | 至少 1 个 | 改之前的旧值,回滚和恢复靠它 |
日志文件不是「数据的副本」,是「改之前的样子」。
回滚靠它、恢复到某个时间点也靠它。所以日志和数据必须分开放——
放同一块磁盘,磁盘一坏两个一起没,日志就白记了。
③ 用 SQLite 复刻「数据文件 + 日志文件」
--------------------------------------------------------------------
刚建完,两个文件:
shop.mdf 49152 字节 (数据)
shop.mdf-journal 0 字节 (日志,提交后就删掉了)
切成 WAL 模式再写一次:
shop.mdf 49152 字节 (数据)
shop.mdf-wal 4152 字节 (日志,攒着没合并)
shop.mdf-shm 32768 字节 (共享内存索引)
这就是「数据文件 + 日志文件」的最小模型:写操作先落日志,再慢慢合并进数据文件。
SQL Server 的 .mdf 和 .ldf 是同一种分工,只是它的日志不会「提交就删」,
而是一直往后追加,直到你做日志备份或者切到简单恢复模式。
④ 文件组:把冷数据放到另一块磁盘
--------------------------------------------------------------------
主文件 shop.mdf 49152 字节 tb 表 2001 行
次文件 hist.ndf 20480 字节 tb_old 表 500 行
SQL Server 里这叫「文件组」:建表时指定 ON 哪个组,这张表就落在那个组的文件上。
好处是备份可以只备主组——冷数据那个组一周备一次就够。
教材 4.1 提了一句「文件组便于管理」,指的就是这个用法。
第一段最能说明「日志是什么」:提交之后 -journal 变成 0 字节——因为那次事务已经安全落盘,
日志的活儿干完了,就该删。
第二段切成 WAL 模式,日志就攒着不删了,一直有内容。
这两种模式对应 SQL Server 的两种恢复模式:简单恢复(日志用完就截断)和完整恢复(日志一直留着,
直到你备份)。
第三段那个 hist.ndf 是第二个文件,它自己独立长大,跟主文件互不影响。这就是文件组的作用:
把不同的数据放在不同的文件(不同的盘)上。
注意这里的「文件组」是复刻出来的语义,不是 SQL Server 的原生机制。
SQLite 用 ATTACH 挂另一个文件,效果上等价于「这张表落在那个文件里」,但它没有文件组的备份、还原、
在线迁移这些能力。
30 秒实验
- 打开 SSMS,展开「数据库 → 系统数据库」,右键
tempdb看属性。 它的「文件」页里,数据文件和日志文件都在,但重启实例之后就全空了。 - 建一个测试库,什么都不做,直接去看它的
.mdf有多大。 你会发现不是 0——初始化时就占了一块。这个块就是SIZE参数。 - 想一想:为什么日志文件必须和数据文件分盘? 因为日志的用途是「磁盘坏了之后恢复」,跟数据放一起,坏了就一起没了。
常见误区
- 「日志文件是数据的备份」。不是。 日志记的是「改之前的样子」,不是「改之后的样子」。要恢复成最新状态,得先还原备份, 再把日志重放一遍——两步,不是一步。
- 「日志没用,直接删了省空间」。删了就没法回滚、没法恢复到某个时间点。 正确做法是备份日志或者切简单恢复模式, 让 SQL Server 自己截断——它只会截断「已经用过」的部分。
- 「文件组越多越专业」。中小型库一个组就够。 分组的成本在管理上:加文件、调增长、算容量,每组一套。没有明确的冷热分层需求,就不要分。
自测三问(点开对答案)
1. 五个系统库里,哪一个坏了会导致整个实例起不来? —— **master。** 它记着实例上有哪些数据库、有哪些登录名、配置是什么。 所以装完的第一件事是备份 master——它不大,但没有它什么都干不了。 2. `.mdf`、`.ndf`、`.ldf` 各是什么? —— **`.mdf` 主数据文件,每个库一个,必须有;`.ndf` 次要数据文件,可以没有,也可以有多个;** **`.ldf` 事务日志,至少一个。** 数据文件存当前值,日志文件存改之前的旧值。 3. 为什么说「日志文件不是数据文件的备份」? —— **因为方向相反。** 数据文件是「改成什么样」,日志是「改成这样之前是什么样」。 恢复的时候是「先还原数据文件的旧备份,再用日志把中间的过程重放一遍」,两步合起来才恢复。下一篇 4.2 讲命名规则。 看着是琐碎的一节,但它是「为什么我的建表语句报语法错误」的标准答案——名字里带横线、带空格、 是保留字,各有各的解法。
- ☐ (本篇完,下一篇 4.2 SQL Server 的命名规则)