数据库规范化可以用一句话概括:“同一个事实只存一处。” 而系统地做到这一点的方法,就是 1NF → 2NF → 3NF。

它为什么重要?——因为如果同一条信息分散在多行里,只改了一处、漏改了其他地方,数据立刻就会自相矛盾。这种情况叫做更新异常。下面你可以亲自体验一下。

Update Anomaly — Try It

Prof.A moved to Room 502. Click the cell to update it. Notice: you have to change every row — miss one and the data contradicts itself.

Before (unnormalized)
studentcourseinstructorofficegrade
S1 KimMATH101Prof.ARoom 301 ← clickA
S1 KimCS102Prof.BRoom 405B+
S2 LeeCS102Prof.BRoom 405A
S3 ParkMATH101Prof.ARoom 301 ← clickB
0/2 updated
After (3NF)
Instructorsinstructor_idinstructor_office
Prof.ARoom 502
Prof.BRoom 405
In 3NF, you update exactly 1 row in the Instructors table. Done.

如果 Prof.A 的办公室变了,在非规范化表里,你必须手动把 2 行都改掉。只要漏掉一行,就会出现“Prof.A 既在 Room 301,又在 Room 502”这样的矛盾。而在 3NF 表里,只改 1 行就结束了。

这其实就是规范化的全部核心——剩下的内容,不过是在判断“哪些列应该放在哪里,才能保证只存一处”的规则。

函数依赖——规范化的判断标准

规范化要看的,是“这一列依赖于谁”。这就叫函数依赖

比如 student_id → student_name 的意思是:“只要知道学生 ID,姓名就能确定。”

看下面图里的箭头颜色:

  • 绿色 = 依赖于整个键(正常)
  • 黄色 = 只依赖于键的一部分(部分依赖 → 违反 2NF)
  • 红色虚线 = 通过非键列间接依赖(传递依赖 → 违反 3NF)
Functional Dependencies at a Glance

Enrollment(student_id, course_id, student_name, course_title, instructor_id, instructor_office, grade)

student_idPrimary Keycourse_idPrimary Keystudent_namecourse_titleinstructor_idinstructor_officegrade
Full dependency (on entire key)Partial dependency (on part of key)Transitive dependency (via non-key)

去掉黄色和红色箭头 = 拆表 = 规范化。

一次看懂 1NF → 2NF → 3NF

下面可以自己点一点,看看表是如何一步步拆开的。红色单元格表示“这一列不应该放在这里”。

Normalization Step-by-Step
Everything in one table. Multiple values in one cell.
Enrollment
student_id 🔑student_namecoursesgrades
S1KimMATH101, CS102A, B+
S2LeeCS102A
S3ParkMATH101, CS102, PHY201B, A, A-
Problem: courses column has multiple values crammed into one cell.
Fix: Split into one row per student-course pair.

1NF:一个单元格里只能有一个值

如果一个单元格里放了 MATH101, CS102 这种多个值,不仅难查,也没法 JOIN。

规则:所有单元格都必须是原子的(只有一个值)。

phone1, phone2, phone3 这样的列,本质上也是同样的问题——只是把值藏进了列名里而已。

2NF:消除部分依赖

在复合键 (student_id, course_id) 的表中,student_name 只依赖于 student_id。它不是依赖整个键,而只是依赖其中一部分——这就是部分函数依赖

规则:所有非键列都必须依赖于整个键

如果存在部分依赖,就把对应的列拆出去,放到单独的表里。

如果键只有一列呢?那就不可能出现部分依赖,所以只要满足 1NF,就自动满足 2NF。

3NF:消除传递依赖

在 Courses 表中,instructor_office 并不直接依赖 course_id(键)。它是通过 course_id → instructor_id → instructor_office 这条路径确定的——也就是说,非键列依赖于另一个非键列。这就是传递依赖

规则:非键列必须只通过键来依赖,不能经过其他非键列。

解决方法:把 instructor_office 拆到 Instructors 表中。

别死记,记住一个问题就够了

“所有非键属性都必须依赖于键、依赖于整个键、并且只依赖于键。”

这句话就是全部:

  • 依赖于键 = 1NF(事实必须能被键识别)
  • 依赖于整个键 = 2NF(依赖的是整个键,而不是键的一部分)
  • 只依赖于键 = 3NF(直接依赖键,而不是经过非键列)

考试里遇到规范化题时,只要问这一句:“这一列到底依赖于谁?是整个键、键的一部分,还是某个非键列?”

常见误区

“做到 3NF 就结束了” —— 3NF 是一个很好的起点,但不是终点。有些场景还需要更严格的 BCNF;反过来,也有些场景会为了性能而故意做反规范化。

“表拆得越多越好” —— 规范化的目的不是增加表的数量,而是让“每张表只负责一种事实”。如果没有依赖关系上的理由却硬拆,只会让 JOIN 更复杂。

“NoSQL 就不需要规范化” —— 规范化是关系型 DB 里的术语,但“如何保持重复数据的一致性”这个问题,在任何 DB 里都存在。即使要做反规范化,也应该清楚自己放弃了什么。

自己检查一下

挑你项目里的一张表,按下面做:

  1. 先写出它的键是什么。
  2. 检查每个非键列,是依赖整个键,还是只依赖键的一部分。→ 如果是后者,就违反 2NF。
  3. 检查有没有非键列依赖于其他非键列。→ 如果有,就违反 3NF。

只靠这 3 步,你就能发现大多数规范化问题。

需要解题帮助?

上传你的问题,几秒钟内获得经过验证的分步解答。

打开 GPAI Solver →