数据库三大范式是设计关系型数据库的基础准则,目的是减少数据冗余、避免更新异常,让数据库结构更合理、更稳定。

冗余

冗余 = 同一份数据,在多张表 / 多行里重复存储。
表:teacher_course(teacher_id, course_id, teacher_name)

数据长这样:

teacher_id course_id teacher_name
1 1001 张三
1 1002 张三
1 1003 张三
2 1001

李四

张三 这三个字,重复存了 3 次。这就叫 数据冗余。
冗余本身不是罪,但冗余会带来连锁灾难。

更新异常

一共有 3 种
1. 更新异常(修改异常)—— 最典型
现在业务需求:张三改名叫张小三。你必须把 所有出现 “张三” 的行全部改一遍,一共 3 行。
如果漏改了一行,就会出现:有的行是 张三;有的行是 张小三
数据不一致 = 业务 BUG。这就是 更新异常。
2. 插入异常
我现在新来了一个老师:王五,teacher_id=3,还没分配任何课程。
但是这张表的主键是 (teacher_id, course_id),主键不能为空。
你想插入王五,但你没有 course_id,你插不进去!
你想存一个老师信息,却被课程绑定住,无法独立插入。
3. 删除异常
场景:课程 1001 停办了,我要删掉 course_id=1001 的记录。
但是删完之后你会发现:李四 这条数据也跟着没了!
因为李四只教 1001 这一门课。你删课程,把老师信息也连带删了。
你只想删课程,结果把老师数据也删没了,这叫 删除异常。

从而是:数据冗余 → 导致三种异常:修改难、插入难、删除难。
通过合理拆分表,消除冗余,从而消灭更新异常。

1、 第一范式(1NF):原子性

核心要求:每一个字段只能包含一个值 反例:一个 contact 字段同时存 电话, 邮箱,就违反了 1NF 正例:拆分成 phone 和 email 两个独立字段,每个字段只存一个值。

作用:保证数据的最小粒度

2、第二范式(2NF):唯一性

前提:必须满足第一范式(1NF)

核心要求:非主键字段必须完全依赖于整个主键,而不能只依赖于主键的一部分 反例:订单表 order 有联合主键 (order_id, product_id),同时包含 product_name 字段。product_name 只依赖于 product_id,不依赖整个主键,这就违反了 2NF 正例:把 product_name 移到商品表 product 中,订单表只保留 order_id 和 product_id。

作用:消除部分依赖,减少数据冗余和更新异常。

3、第三范式(3NF):传递性

1)前提:必须满足第二范式(2NF)

2)核心要求:非主键字段之间不能存在传递依赖,即非主键字段必须直接依赖于主键,而不能依赖于其他非主键字段

3)反例:订单表 order 有主键 order_id,包含 customer_id 和 customer_city。customer_city 依赖于 customer_id,而非直接依赖 order_id,这就违反了 3NF。 正例:把 customer_city 移到客户表 customer 中,订单表只保留 order_id 和 customer_id。

4)作用:消除传递依赖,进一步减少冗余,让数据更新更可靠。

一句话总结三大范式

1NF:列不可再分(原子性)

2NF:非主键字段完全依赖主键(唯一性)

3NF:非主键字段不传递依赖(独立性)

4、BCNF

BCNF 被看作是对第三范式(3NF)的增强和补充 。

第三范式(3NF)要求非主键字段不能传递依赖于主键,但它没有解决主键内部的依赖问题

如果一张表的主键是联合主键,而联合主键中的某一部分,被另一部分所决定,那么即使满足 3NF,依然存在数据冗余和更新异常。

BCNF 就是为了解决这个问题而提出的。

===>

前提:必须满足第三范式(3NF)。

核心要求:在关系模式中,每一个决定因素都必须是候选键。

换句话说,不仅非主键字段不能依赖其他非主键字段,主键的一部分也不能依赖另一部分。

实践中的权衡

在实际开发中,我们不一定严格遵守三大范式。为了提升查询性能,有时会反范式设计(比如冗余存储一些字段),用空间换时间,减少复杂的关联查询。

举例:

假设我们有一个 courses 表,记录课程和老师:

course_id teacher_id teacher_name
101 1 张三
102 1 张三
103 2 李四
  • 这里 course_id 是主键(满足 1NF、2NF、3NF)

  • teacher_name 依赖于 teacher_id,而 teacher_id 不是候选键

  • 所以这张表违反了 BCNF

解决方法是拆分成两张表:

  1. teachers 表:(teacher_id, teacher_name)

  2. courses 表:(course_id, teacher_id)

这样,每个表的决定因素都是其主键,就满足了 BCNF。

其实我觉得这些东西你在实际应用的时候,不用像学术那样去说:这是什么组件,你会发现; (teacher_id course_id teacher_name)不符合2nf之后,你自然的就会把teacher_name放到另一张表(teacher 表)中来解决这个问题;然后又发现不符合BCNF,所以你自然的就会把teacher_id也放到teacher 表中。

真实开发中,99% 的场景,只要你把 2NF、3NF 做好,表就自动满足 BCNF

讨论

1、我们并不需要死抠教材里那些刻意构造的反例,也不用强行套范式定义
2、范式不是教条,是对 “好设计” 的事后总结。

3、好的数据库设计,靠常识不靠玄学,靠归属不靠背诵。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐