返回文章列表

数据库设计

数据库表设计范式

该怎么设计一张good表……

2026 年 05 月 13 日33296published
#数据库

数据库表设计范式

一、引言:为什么需要规范化?

在设计数据库表的过程中,相信大家或多或少会有如下疑问:我设计的表规范吗?这样设计字段对吗?有没有什么问题我在设计的时候发现不了,到了实际生产时才暴露出来?

为了解决上述问题,一个伟大的计划被提出来:有没有一种可能,只要我们在设计表的时候,遵从某些规范,就能尽量避免问题的出现?而今天要讲的数据库表设计范式,就是这样一种解决方案。

我们首先来看一个失败的设计方案:

学生选课表(学号, 学生姓名, 所在学院, 课程号, 课程名, 教师名, 成绩)

其中主键是(学号,课程号);乍一看,这张表设计的似乎挺好:一张表涵盖了挺多字段,起码挺热闹;然而,当我们插入一些数据的时候,灾难发生了:

学号学生姓名所在学院课程号课程名教师名成绩
001张三软件学院C01数据库王老师90
001张三软件学院C02操作系统李老师85
001张三软件学院C03计算机网络赵老师88

可以看到,001 -> 张三, 软件学院这部分数据重复出现了很多次,这在数据库表设计中称为数据冗余;同理,如果我们想要修改某一列数据,比如说修改张三所在学院,就要同时修改三行记录中的同一处数据;这叫做更新异常

如果上述问题还在忍受范围内,只要脸皮厚,多存一点数据、多改几次表并不是完全不可以,下面的问题就是灾难中的灾难:如果我们想插入一条课程:综合程序设计,我们会惊奇的发现:插入不了!这是因为没有学生选课时,我们无法单独插入课程信息,这叫做插入异常;同理,如果删除最后一条选课记录时,我们同样会惊奇的发现:这门课的课程信息也丢失,这叫做删除异常

其实要解决上述问题很简单:拆表。将原有表拆成

学生表(学号, 学生姓名, 所在学院)
课程表(课程号, 课程名, 教师名)
选课表(学号, 课程号, 成绩)

上述问题就会迎刃而解。可接下来的问题是:我怎么知道该如何拆表呢?规范来说,我们的核心目的是:

通过合理拆分关系模式,减少数据冗余,避免插入、删除、更新异常,提高数据库结构的合理性。

而范式,就是通过一系列规则来告诉我们该如何科学拆表。


二、第一范式

在正式介绍范式前,我们必须要了解一些基本的知识:

中文术语含义也常被叫作
超码能唯一确定一条记录的属性集超键
候选码最小的超码候选键
主码从候选码中选出来作为主键的码主键
主属性出现在某个候选码中的属性主属性,一般不叫“主键属性”
非主属性不出现在任何候选码中的属性非主属性

有人可能有疑惑:主键我明白,候选键我知道,超键是个啥玩意?其实这三者很类似,只不过超键的范围要大一点,可以这么看:

超键 ⊃ 候选键 ⊃ 主键

简单对比一下:

  • 超键:能唯一定位一行就行,可以有多余字段。

  • 候选键:没有多余字段的超键。

  • 主键:从候选键中选出来,真正作为表中主要标识的那个键。

接下来我们看看什么是第一范式。

第一范式(1NF)要求:关系中的每个属性值都必须是原子的,不可再分。

比如这个例子:学生表(学号, 姓名, 联系方式),该关系满足1NF吗?显然不满足,因为联系方式可以拆分为电话邮箱等等:

学号姓名联系方式
1001张三13800000000, 123456@qq.com
1002李四13900000000, 微信: lisi123

一个字段里塞了多个联系方式,就不满足1NF。


三、第二范式

在满足了第一范式的基础上,我们来看看第二范式。

第二范式(2NF)要求:在满足 1NF 的基础上,所有非主属性都必须完全函数依赖于候选键,不能只依赖候选键的一部分。

这里的非主属性前文提到过,就是不出现在任何候选键中的属性。

例如这个关系选课表(学号, 课程号, 学生姓名, 学院, 课程名, 成绩),主键和候选键都是(学号, 课程号)能唯一确定一行,如果其他列都能满足必须完全依赖(学号, 课程号),那么这个关系满足2NF。然而我们发现,学生姓名学院 只依赖 学号课程名 只依赖 课程号,都属于部分依赖,因此这个关系不满足2NF。如果改造,可以这么改:

学生表(学号, 学生姓名, 学院)
课程表(课程号, 课程名)
选课表(学号, 课程号, 成绩)

需要注意的是,2NF针对的是联合主键,如果一个关系的候选键只有一个属性,那么它天然不存在“部分依赖”问题,因此只要满足 1NF,通常就满足 2NF。简单说,就是只要你的关系里的主键只有一个属性,自动满足2NF。


四、第三范式

第三范式(3NF)要求:在满足 2NF 的基础上,非主属性不能传递依赖于候选键。

这里的重点是传递依赖,什么意思呢?

我们还是以学生表举例:学生表(学号, 学生姓名, 所在院系, 院长),我们以学号为主键,此时该关系存在以下依赖:

学号 -> 学生姓名, 所在院系
所在院系 -> 院长

这里可以看到:尽管学号直接决定了学生姓名, 所在院系,但院长却是由所在院系间接决定的,因此它违反了3NF。要改的话可以拆成:

学生表(学号, 学生姓名, 所在院系)
学院表(所在院系, 院长)

在实际业务开发中,很多表设计到 3NF 基本已经足够,但为了应对期末考试,接下来还要介绍另外两种范式。


五、BCNF范式

巴斯-科德范式(BCNF)要求:对于关系模式 R 中的每一个非平凡函数依赖 X → YX 都必须是 R 的超键。

这里出现了一个新名词:非平凡函数依赖,它指的是X → Y并且 Y 不是 X 的子集。例如:学号 → 姓名是非平凡函数依赖,但:{学号, 姓名} → 姓名是平凡函数依赖,因为右边的 姓名 本来就包含在左边里面。

我们举一个经典的满足 3NF 但不满足 BCNF 的例子:学生选课教师表(学号, 课程号, 教师),其中一个学生可以选多门课,一门课可以由多个老师教,每个老师只教一门课;显而易见主键是(学号, 课程号),函数依赖为(学号, 课程号) → 教师教师 → 课程号,由第二个函数依赖可看出:教师决定课程号,但教师并不是超键,因为教师字段无法确定唯一行;因此这个关系不满足 BCNF,可以拆为:

教师课程表(T, C)
学生教师表(S, T)

需要注意的是,BCNF 作为增强 3NF,相比于 3NF 更严格,但分解后可能导致:

  • 某些函数依赖无法直接保持
  • 查询时需要更多连接操作
  • 表结构更加分散

因此实际工程中不一定盲目追求 BCNF。


六、第四范式

第四范式(4NF)要求:如果关系满足BCNF范式,并消除了多值函数依赖,该关系满足第4范式。

第四范式主要用来处理多值依赖,这里的多值依赖是数据库规范化里的一种依赖关系,主要用来描述:一个属性确定的不是一个值,而是一组彼此独立的多个值。

例如:

学生(学号, 爱好, 语言)

如果一个学生有多个爱好,也会多门语言,那么爱好和语言之间没有直接关系,但会产生组合冗余。

可以拆成:

学生爱好表(学号, 爱好)
学生语言表(学号, 语言)

七、实际设计时怎么用范式?

  1. 先保证字段原子性:满足 1NF
  2. 如果有联合主键,检查是否存在部分依赖:满足 2NF
  3. 检查非主属性是否依赖另一个非主属性:满足 3NF
  4. 复杂关系再考虑 BCNF / 4NF
  5. 工程中允许有条件反规范化,但要知道自己为什么这么做

上一篇

已经是第一篇了

下一篇

已经是最后一篇了