数据库三大范式&&BCNF
本文介绍了冗余和异常的含义,感受到这真的很烦,故而提出数据库的三大范式(1NF,2NF,3NF)和BCNF来解决问题;并举了个例子来剖析解决过程。但是实际应用还是集中在数据库设计上,设计者无需纠结教材上的刻意反例,只需靠直觉和常识设计: 你只要按 “谁属于谁,就放谁表里” 去设计,根本不会遇到那种诡异的 BCNF 反例 。
数据库三大范式是设计关系型数据库的基础准则,目的是减少数据冗余、避免更新异常,让数据库结构更合理、更稳定。
冗余
冗余 = 同一份数据,在多张表 / 多行里重复存储。
表: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
解决方法是拆分成两张表:
-
teachers表:(teacher_id, teacher_name) -
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、好的数据库设计,靠常识不靠玄学,靠归属不靠背诵。
更多推荐
所有评论(0)