在做 MySQL 库表设计的时候,很多人一开始都是随性设计,前期考虑不周,表结构划分模糊。经常写着业务代码,中途就要重构单张表,严重的时候需要大面积修改多张关联表。这种随心所欲的表结构设计,会给后续开发、维护埋下大量隐患。
范式(Normal Form),就是数据库表设计时需要遵守的一套规范原则。按照范式规范设计库表,可以在项目初期就搭建出合理、优雅的表结构。数据库范式包含我们常说的三大范式,还有 BCNF 巴斯 - 科德范式、4NF 第四范式、5NF 第五范式,以及域键范式等;与之对应的还有工程上常用的反范式设计。
范式是递进关系:高一级范式建立在前一级范式之上,顺序不可颠倒。就像先炒菜、再吃饭、最后洗碗,不能颠倒。2NF 必须建立在满足 1NF 的基础上,如果表连第一范式都不满足,自然不可能满足第二范式、第三范式。
1.1 第一范式(1NF)
核心原则:原子性,字段内数据不可再拆分
如果一张学生表里面存在一个student字段,字段内容同时包含姓名、性别、身高,这条数据就还能继续拆分,明显不满足第一范式。 符合 1NF 的改造方式:拆分字段,单独设置name、gender、height,每一列的值都是不可拆分的最小单元。
不满足第一范式带来的问题:
- 后端程序和数据库字段很难做映射;
- 查询出来数据后,代码里还要额外对字段内容做字符串拆分处理;
- 插入数据前,需要先把多段信息拼接成一个字符串再写入,操作繁琐。
1.2 第二范式(2NF)
核心原则:所有非主键列完全依赖主键,一张表只描述一件事
假设一张表同时存放学生基础信息、课程名称、考试分数,虽然字段已经满足 1NF 原子性,但是course课程、score分数并不完全依赖学生主键,会造成学生基础信息大量重复冗余。
优化方案:拆分为学生表、课程表、成绩表,每张表设置独立主键 id,表中其余字段全部依赖本表主键。 每张表只负责一类业务,不再一张表里混合存储多种业务的数据。
1.3 第三范式(3NF)
核心原则:消除传递依赖,非主键字段之间不能互相依赖
举个例子:学生表字段:id、name、department院系、dean院长。 院长是由院系决定的,dean依赖department,而不是直接依赖学生主键 id,这就是传递依赖,不满足 3NF。
优化方案:拆分出院系表,学生表只保存院系 id,院系表存放院系名称、院长信息。
如果不做第三范式拆分,会出现这些问题:
- 院系更换院长时,需要批量修改所有该院系学生的记录;
- 删除一条院系院长记录,可能连带误删学生信息;
- 数据冗余,维护成本极高。
3NF 目标:表中所有非主键字段,只依赖主键,非主键之间互相独立,不存在依赖关系。
1.4 三大范式小结
- 1NF:原子性,字段值不可再拆分;
- 2NF:唯一性,一张表只描述一类业务,字段全部依赖主键;
- 3NF:独立性,消除非主键字段之间的传递依赖;
不遵循三大范式的通病:
- 数据大量冗余,占用更多存储空间;
- 表结构臃肿,新增数据需要重复录入大量相同内容;
- 更新关联信息时,需要批量修改多条记录,极易引发数据不一致。
遵循范式设计之后,数据表结构清晰、冗余少,扩展性和灵活性更强。
1.5 巴斯 - 科德范式(BCNF / 3.5NF)
第三范式只约束非主键字段不能互相依赖,但是没有约束联合主键内部字段的依赖关系,BCNF 就是用来弥补这个缺陷,也叫 3.5NF。
BCNF 规则:主属性(联合主键里面的字段)之间,也不能存在依赖关系。
示例:一张学生表使用班级、班主任、学生姓名作为联合主键,但是班主任本身是由班级决定的,班主任依赖班级,也就是联合主键内部存在依赖,不符合 BCNF。
问题:
- 班级更换班主任,需要修改该班级下全部学生记录;
- 删除班主任记录,连带删除学生数据;
- 新增班级时,必须同时录入学生信息(主键不允许为空)。
优化:拆分为班级表、学生表。学生表存放班级 id,使用班级 id + 学生姓名作为联合主键;班级表维护班级名称、班主任。 修改班主任,只需要修改班级表一条记录,不再影响学生数据。
一句话总结:3NF 管非主键,BCNF 连联合主键内部的依赖也要消除。
1.6 第四范式(4NF)
核心:消除多值依赖 多值依赖:一张表里存在三个及以上独立字段,一个字段的值,需要同时由另外多个字段共同决定。
经典场景:用户 - 角色 - 权限。一个用户可以有多个角色,一个角色可以绑定多条权限。如果全部放在同一张表,就产生多值依赖。
按照 4NF 优化,就是我们后端开发耳熟能详的权限五表设计: 用户表、角色表、权限表、用户角色中间表、角色权限中间表。
拆分之后:
- 基础表没有冗余;
- 新增、删除角色 / 权限,互不干扰;
- 通过中间关系表维护多对多关联。
1.7 第五范式(5NF,完美范式)
在 4NF 基础之上,消除连接依赖,保证表的无损连接。 5NF 在实际业务中极少遇到,识别难度很高,日常开发几乎用不到,仅作为理论了解即可。
1.8 域键范式(6NF)
被称为终极范式,目前大多停留在学术研究层面,生产环境几乎不会使用。
1.9 反范式设计
范式的优点很明显:减少冗余、节省磁盘、结构清晰,降低增删改异常风险。 但是范式不断拆分表,带来一个致命缺点:查询关联数据时需要大量联表JOIN。 表关联数量一多,查询性能急剧下降,甚至引发索引失效,在高并发业务下,联表开销是无法忽视的性能瓶颈。
反范式:主动、有目的地打破范式约束,允许适度数据冗余,换取查询性能。
重点区分:不是表不小心违反范式就叫反范式;反范式是开发人员明确知道会产生冗余,经过评估,收益大于弊端,主动做出的设计。
设计准则:业务优先,范式只是工具,不是铁律。能适配业务、满足性能要求的表结构,就是好结构。
2.1 范式整体总结
范式级别越高,约束越严格,表拆分得越细碎,理解和维护成本也越高。 在绝大多数企业项目中,表结构满足 3NF 或者 BCNF 就足够了,平衡冗余和查询性能;4NF 一般多用于多对多复杂关系(权限系统等),5NF、域键范式基本不用。
✅ 不合理表结构带来的问题
- 数据冗余,浪费存储空间
- 增删改操作容易出现数据异常,维护困难
✅ 合理范式设计带来的收益
- 存储节省,内存、磁盘开销更低
- 数据划分清晰,数据库整体稳定性更好
- 结构易于迭代维护,减少脏数据
范式速查表
| 范式 | 核心要求 |
|---|---|
| 1NF | 原子性,字段值不可拆分 |
| 2NF | 唯一性,字段全部依赖本表主键,一表一事 |
| 3NF | 独立性,消除非主键之间传递依赖 |
| BCNF | 联合主键内部字段,不能互相依赖 |
| 4NF | 消除表内多值依赖,处理多对多场景 |
| 5NF | 消除连接依赖(极少使用) |
| 域键范式 | 理论上的终极范式,无落地价值 |
- 各范式之间的递进关系图:
