数据库三大范式通俗解读:用实例讲清“为什么不能乱建表”
场景设定:学生选课系统
假设我们设计一张表存储所有数据:
CREATE TABLE 学生选课 (
学号 INT,
姓名 VARCHAR(20),
系名 VARCHAR(20),
系主任 VARCHAR(20),
课程号 INT,
课程名 VARCHAR(20),
成绩 INT
);
数据示例:
| 学号 | 姓名 | 系名 | 系主任 | 课程号 | 课程名 | 成绩 |
|---|---|---|---|---|---|---|
| 101 | 张三 | 计算机 | 王老师 | 301 | 数据库 | 90 |
| 101 | 张三 | 计算机 | 王老师 | 302 | 算法 | 85 |
| 102 | 李四 | 数学 | 刘老师 | 302 | 算法 | 95 |
第一范式(1NF):原子性
要求:每列不可再分,消除重复组
问题:如果“课程”字段存储多值(如 "数据库,算法"),会导致查询困难。
解决:确保每列只存单一值(已满足)。
违反后果:
- 无法直接查询“张三的数据库成绩”
- 统计成绩需拆分字符串,效率低下
第二范式(2NF):完全依赖
要求:非主属性必须完全依赖于主键(消除部分依赖)
主键:(学号, 课程号)(联合主键)
问题:
- 姓名、系名只依赖学号(部分依赖)
- 课程名只依赖课程号(部分依赖)
数据冗余:
- 张三的系名重复存储(计算机系出现2次)
- 算法课程名重复存储(出现2次)
解决:拆表消除部分依赖
-- 学生表(主键:学号)
CREATE TABLE 学生 (
学号 INT PRIMARY KEY,
姓名 VARCHAR(20),
系名 VARCHAR(20)
);
-- 课程表(主键:课程号)
CREATE TABLE 课程 (
课程号 INT PRIMARY KEY,
课程名 VARCHAR(20)
);
-- 选课表(主键:(学号,课程号))
CREATE TABLE 选课 (
学号 INT,
课程号 INT,
成绩 INT,
PRIMARY KEY (学号, 课程号)
);
第三范式(3NF):消除传递依赖
要求:非主属性不能依赖其他非主属性
问题:在学生表中:
- 系主任依赖系名(传递依赖)
- 若计算机系更换主任,需修改所有该系学生的记录
数据异常:
- 更新异常:修改一个系的主任,需更新多条记录
- 插入异常:新成立中文系(尚无学生),无法存入系主任
- 删除异常:删除所有数学系学生,系主任信息丢失
解决:拆表消除传递依赖
-- 学生表(主键:学号)
CREATE TABLE 学生 (
学号 INT PRIMARY KEY,
姓名 VARCHAR(20),
系名 VARCHAR(20) -- 只存系名
);
-- 院系表(主键:系名)
CREATE TABLE 院系 (
系名 VARCHAR(20) PRIMARY KEY,
系主任 VARCHAR(20)
);
最终设计(符合3NF)
| 表名 | 字段 | 主键 |
|---|---|---|
| 学生 | 学号, 姓名, 系名 | 学号 |
| 院系 | 系名, 系主任 | 系名 |
| 课程 | 课程号, 课程名 | 课程号 |
| 选课 | 学号, 课程号, 成绩 | (学号, 课程号) |
优势:
- 零冗余:系主任只存1次(无论多少学生)
- 更新安全:修改系主任只需改1条记录
- 删除安全:删除学生不影响院系信息
- 插入灵活:可单独添加院系(无学生时)
核心结论:乱建表会导致数据冗余、操作异常和维护灾难。三大范式通过拆分表,用空间换稳定性,是高效数据库设计的基石。

被折叠的 条评论
为什么被折叠?



