mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1769 字
5 分钟
数据库系统:从 SQL 到范式的一趟梳理
2026-07-02

最近在学数据库系统,把课上反复出现的 SQL 写法、设计题思路和范式分析串成一篇笔记,方便自己以后回看,也分享给同样在学这门课的朋友。下文以 MySQL 8.0 为主要参考,字段名若含 #、中文或关键字,建议用反引号包裹。

一、SQL 在干什么:建表、改表与增删改#

1. 建表时常见约束#

教学库里典型的三张表是 学生 S课程 C选课 SC。建表时我一般会同时想清楚主键、外键和检查约束:

CREATE TABLE S (
`S#` VARCHAR(10) PRIMARY KEY,
SNAME VARCHAR(50) NOT NULL,
AGE INT CHECK (AGE BETWEEN 0 AND 120),
SEX CHAR(1) CHECK (SEX IN ('M','F','男','女'))
);
CREATE TABLE SC (
`S#` VARCHAR(10),
`C#` VARCHAR(10),
GRADE DECIMAL(5,2),
PRIMARY KEY (`S#`, `C#`),
FOREIGN KEY (`S#`) REFERENCES S(`S#`),
FOREIGN KEY (`C#`) REFERENCES C(`C#`),
CHECK (GRADE IS NULL OR GRADE BETWEEN 0 AND 100)
);

ALTER TABLE 用来加列、改类型、删列、加重命名;MySQL 8+ 可用 RENAME COLUMN。插入、更新、删除就是熟悉的 INSERT / UPDATE / DELETE

2. 单表查询的几个固定套路#

  • 条件过滤WHERE
  • 分组统计GROUP BY + HAVING(过滤的是分组后的结果,不是行)
  • 排序分页ORDER BYLIMIT n OFFSET m

例如「统计每门课选修人数,只显示超过 15 人的课,按人数降序、课号升序」:

SELECT `C#`, COUNT(*) AS 人数
FROM SC
GROUP BY `C#`
HAVING COUNT(*) > 15
ORDER BY 人数 DESC, `C#` ASC;

3. 多表查询:连接与子查询#

显式 JOIN 可读性通常更好:

SELECT s.SNAME, sc.GRADE
FROM S s
JOIN SC sc ON s.`S#` = sc.`S#`
WHERE sc.`C#` = 'C1'
ORDER BY sc.GRADE DESC;

「某同学不学的课程」这类题,我更喜欢 NOT EXISTS,语义接近「不存在选课记录」:

SELECT c.`C#`
FROM C c
WHERE NOT EXISTS (
SELECT 1
FROM S s
JOIN SC sc ON s.`S#` = sc.`S#`
WHERE s.SNAME = 'WANG'
AND sc.`C#` = c.`C#`
);

NOT IN 也能写,但要注意子查询里出现 NULL 时结果可能不符合直觉;考试和工程里 NOT EXISTS 更稳

聚合在多表场景很常见,例如「李文老师所授每门课的平均分与最高分」:

SELECT c.`C#`, c.CNAME,
AVG(sc.GRADE) AS 平均成绩,
MAX(sc.GRADE) AS 最高成绩
FROM C c
JOIN SC sc ON c.`C#` = sc.`C#`
WHERE c.TEACHER = '李文'
GROUP BY c.`C#`, c.CNAME;

二、关系代数与 SQL 的对应(心里有个映射)#

关系代数SQL 里大致对应
选择 σWHERE
投影 πSELECT
连接 ⋈JOIN
并 ∪UNION
差 −NOT EXISTS / 外连接过滤

比如「WANG 不学的课号」用代数可写成:πC#(C)πC#(σSNAME=WANG(S)SC)\pi_{C\#}(C) - \pi_{C\#}(\sigma_{SNAME='WANG'}(S) \bowtie SC)。写 SQL 时不一定要先写代数,但差集类问题先想「全集减去已选集合」会少绕弯。


三、数据库设计:从 E-R 到关系模式#

设计题的核心步骤我习惯固定成三步:

  1. 找实体:各自有哪些属性,谁是主标识(主键候选)。
  2. 找联系:1<1>、1、M,联系有没有自己的属性(如借阅日期、成绩)。
  3. 转关系模式:实体各成一张表;1 把「一」方主键放进「多」方作外键;M 单独建联系表,主键常是两端主键的组合。

图书借阅例子(简化):

  • 出版社 —(1)— 书籍:出版社名可放进书籍表作外键。
  • 借书人 —(M)— 书籍:拆成 借阅(书号,借书证号,借书日期,还书日期),两端都是外键。

运动会里「一个裁判组只负责一个比赛项目」往往是 1<1>:可以把裁判组编号放进比赛项目表,并加 UNIQUE 保证一个裁判组只对应一个项目。

商品进销存里供应商—商品、商店—客户—商品可能出现 三元联系,最终也会落成带多个外键的联系表,并想清楚候选码(例如是否要把「销售日期」放进主键,避免同一天多笔交易冲突)。


四、范式:为什么要拆表#

一张「大而全」的表容易出现四类问题:

  1. 冗余:同一学生姓名、系别因多门课重复多行。
  2. 插入异常:还没选课的学生,若主键是(学号+课程号),连基本信息都难插。
  3. 删除异常:删掉某人选的最后一门课,可能连人带课信息一起丢。
  4. 更新异常:改名、改系名要改很多行,容易不一致。

判断范式时我会先写 函数依赖 FD候选码

  • 选课大表 R(学号,姓名,年龄,系别,课程号,课程名,成绩)
    • 学号 → 姓名,年龄,系别
    • 课程号 → 课程名
    • (学号,课程号)→ 成绩
    • 候选码:(学号,课程号)
    • 存在部分依赖(学号、课程号各自决定非主属性)→ 至少要做 3 张表:学生、课程、选课。

学生表带系名(学号→系号,系号→系名)会出现传递依赖,属于 2NF 但不到 3NF,应拆出 系(系号,系名)学生(学号,姓名,年龄,性别,系号)

库存(仓库号,地点,设备号,设备名,库存数量) 典型拆法:

  • 仓库(仓库号,地点)
  • 设备(设备号,设备名)
  • 库存(仓库号,设备号,库存数量)

五、视图、存储过程与触发器(会用即可)#

视图适合统计展示,例如按读者统计借阅册数:

CREATE OR REPLACE VIEW v_bnum (`读者号`, `借阅数量`) AS
SELECT r.`读者号`, COUNT(b.`图书号`)
FROM `读者` r
LEFT JOIN `借阅` b ON r.`读者号` = b.`读者号`
GROUP BY r.`读者号`;

触发器常用来维护一致性:学号在 S 表变更时同步 SC;删学生时级联删成绩;订单插入后扣减商品库存。写之前要弄清 BEFORE / AFTER 以及 NEW / OLD 的含义。

存储过程 / 函数适合封装「按订单号算总金额」「按学号返回性别」这类重复逻辑;注意 DELIMITERIN / OUT 参数。


六、工程里还会碰到什么#

课上综合题常把几块拼在一起,我自己记成四句话:

  • 性能:给 WHEREJOINORDER BYGROUP BY 高频列建合适索引;用 EXPLAIN 看计划;少 SELECT *
  • 安全:最小权限、GRANT/REVOKE、角色;敏感字段加密;视图隐藏列;审计日志。
  • 并发:事务 ACID;隔离级别与脏读、不可重复读、幻读;选课、库存用事务 + 行锁 / 条件更新防超卖。
  • 备份恢复:全量 + 增量/binlog;主从提高读能力;定期做恢复演练。

选课高峰、二手平台大促这类场景,本质都是:索引扛查询、事务扛一致性、备份扛故障


写在最后#

对我来说,数据库这门课适合按 SQL 熟练 → 多表与聚合 → E-R 设计 → 范式分解 → 视图/过程/触发器 → 性能与安全 这条线推进。教学库 S/C/SC、图书借阅、供应商零件 SP、集团人事 EMP/WORKS/COMP 这几类模型反复出现,把「不学的课」「没供应的零件」「没任职的职工」写对,大部分查询题就有套路了。

如果你也在学数据库,欢迎一起交流;文中有错漏也请在评论区指正。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

数据库系统:从 SQL 到范式的一趟梳理
https://sereinz.top/posts/post8/
作者
Serein
发布于
2026-07-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录