数据库设计(Normalization,规范化)
数据库设计规范化
没问题!数据库设计(Normalization,规范化)确实是整门课最核心、最实用的部分,也是面试最爱考的。老师讲得快很正常,别慌,我们一步一步来。
这张幻灯片虽然标题是 “Features of Good Relational Designs”(好的关系设计的特征),但它实际上是在用一个反面教材,来告诉你“如果不做好设计,会发生什么可怕的事情”,从而引出为什么要进行 Normalization(规范化)。
下面我结合图中的内容给你讲讲,同时帮你复习前面的概念:
这张图在干什么?
幻灯片里假设了一种情况:我们把 instructor(教师表)和 department(系/部门表)强行合并成了一个大表 in_dep。
💡 顺手复习概念 1:Natural Join(自然连接) 幻灯片里提到了这个大表是自然连接的结果。简单来说,如果
instructor表里有dept_name(系名),department表里也有dept_name,自然连接就是系统自动找到这两个表里dept_name相同的值,然后把它们像拼图一样左右拼成完整的一大行。
拼完之后,你得到的就是中间那个蓝底的表格。看起来数据很全对吧?但幻灯片指出了这个“大一统”表格的两个致命缺点:
缺点 1:信息重复(There is repetition of information)
你仔细看中间的表格:
- 看
Comp. Sci.(计算机系)那几行。Katz、Srinivasan 和 Brandt 都是计算机系的老师。 - 你看他们对应的
building(办公楼)全都是Taylor,budget(预算)全都是100000。 - 同样,
Physics(物理系)、Finance(金融系)、History(历史系)的办公楼和预算也都被重复写了多次。
为什么这很糟糕? 这不仅仅是浪费硬盘空间的问题。想象一下,如果学校决定把计算机系的预算(budget)从 100000 涨到 150000。在现在的表格里,你必须去找到每一个计算机系的老师,把他们后面的 100000 改成 150000。万一有 100 个老师,你改了 99 个,漏了 1 个,数据库里的数据就“打架”了(同一个系出现了两个不同的预算)。 (专业术语预警:这在后面会叫 Update Anomaly,更新异常)
缺点 2:必须使用空值(Need to use null values)
幻灯片底部括号里举了个例子:“如果我们增加了一个还没有老师的新系”。 假设学校今天刚成立了“Art”(艺术系),分了办公楼和预算,但还没招到老师。
为什么这很糟糕? 如果我们想把“艺术系”的信息存进数据库,因为现在只有一张大表 in_dep,我们只能新增一行。 但因为没有老师,这一行的 ID、name(名字)、salary(工资)我们都没法填!只能填入 NULL(空值)。
💡 顺手复习概念 2:Primary Key(主键)与 NULL 主键是用来唯一确定表格中一行数据的属性(比如老师的 ID)。数据库有一个铁律:主键绝对不能是 NULL。如果 ID 是主键,那么这个没有老师的“艺术系”就根本无法被存进数据库里! (专业术语预警:这在后面会叫 Insertion Anomaly,插入异常)
总结:跟你的 Chapter 7 有什么关系?
这张图就是在告诉你:“把所有东西塞进一张大表里,是一个极度糟糕的设计方案。”
而你现在正在学的 Chapter 7 Normalization(规范化),正是为了解决这个烂摊子!
规范化的核心思想就是:教你如何科学地“拆表”。 通过一系列的规则(你会学到 第一范式 1NF、第二范式 2NF、第三范式 3NF、BCNF 等),老师会教你怎么把这张臃肿的大表,合理地拆分回 instructor 和 department 两个小表。让它们既不会产生数据冗余(重复),也不会出现乱七八糟的 NULL,同时还能通过互相引用(Foreign Key 外键)保持联系。
怎么样,这样结合前面的概念来看,是不是能理解这张图的用意了?如果你在听接下来的范式(1NF, 2NF, 3NF)时遇到听不懂的,随时把图发给我,我继续帮你拆解! /image-20260415081509.png)
哈哈,老师这是在跟你玩“反转”呢!
上一张幻灯片刚吓唬完你:“把两个表合并成一个大表会导致信息重复和各种麻烦(比如插入空值)”。 这张幻灯片马上跳出来说:“但是,并不是所有的合并都会导致信息重复!”
我们来看看这张图在说什么,以及为什么这次合并就是安全的。
老师举的“好合并”例子:
假设我们有两个关于“排课”的表:
sec_class(上课地点表):里面有sec_id(班级号),building(教学楼),room_number(教室号)。—— 它告诉你某个班级在哪里上课。section(开课信息表):里面有course_id(课程号),sec_id(班级号),semester(学期),year(年份)。—— 它告诉你哪门课在哪个学期的哪一年开了哪个班。
现在,老师把它们合并成了一个表:
- 合并后的
section表:course_id,sec_id,semester,year,building,room_number。
老师的结论是:在这种情况下,没有信息重复(No repetition in this case)。
💡 为什么上一张图合并就翻车,这张图合并就没事?(核心重点)
这是 Chapter 7 (Normalization) 要解决的核心思维模式,我用大白话给你对比一下:
🔴 回顾上一张图的翻车原因(Instructor + Department): 在上一张图里,“系(Department)”是一个独立的大实体,它自己有“预算”、“办公楼”等属性。而“老师(Instructor)”只是从属于系的一个个体。 因为多个老师属于同一个系,当你把它们强行拼在一张表里时,为了照顾每一个老师,那个“系”的预算和大楼信息就不得不被抄写很多遍(重复冗余)。
🟢 看看这张图为什么安全(Section + Location): 在这个例子里,(course_id, sec_id, semester, year) 这一大串东西加在一起,唯一确定了“一节具体的课”(比如:2026年秋季学期的计算机101课程第1班)。 而一节具体的课,必定在一个具体的 building 和 room_number 里上。 building 和 room_number 本来就是用来描述“这节具体的课”本身的属性!
合并之后,表里的每一行都代表“一节独一无二的课的完整信息(包含它是什么课、什么时候上、在哪上)”。这里面没有任何多余的“抄写”。 你不会因为增加了另一门课,而去重复写一遍原来那门课的上课地点。
帮你预习 Chapter 7 的专业术语(提前混个脸熟):
老师马上就会教你一个词叫 Functional Dependency(函数依赖),这是 Normalization(规范化)的基础。你可以把它理解为“谁决定谁”:
- 糟糕的设计中:老师的 ID 决定不了系的预算,是“系名”决定了“系的预算”。你把“系的预算”强行塞进以“老师 ID”为主体的表里,就会产生冗余。(这违反了第二/第三范式)
- 好的设计中(本图):这节课的代码+学期+年份,直接决定了它在哪个教学楼的哪个教室上。上课地点就是这节课的附属属性,所以把它们放在一张表里,天经地义。(这符合良好的范式设计)
总结一下老师的用意: 数据库设计(Normalization)不是盲目地把表拆得越碎越好,也不是把所有东西揉成一团。它的目的是“让每一个表只专心描述一件事情(一个实体)”。如果属性本来就属于同一个实体,合并是可以的;如果属于不同的实体(比如老师和系),那就必须拆开!
跟着老师的节奏走,遇到这种对比多琢磨一下“它们描述的是不是同一个核心东西”,Chapter 7 就会变得非常简单!还有啥看不懂的图继续发!
老师这套幻灯片的逻辑可以说是非常严密了!
如果说第一张图是在警告你“不要乱合表”,第二张图告诉你“有的合表没问题”,那么这第三张图就是在教你:“既然大表不好,我们要拆表(Decomposition),但是!千万别乱拆!”
这张图引出了数据库设计中一个极其重要、必考的核心概念:Lossy Decomposition(有损分解)。
我们一步步来看这张图说了什么,并帮你复习关键概念:
1. 拆表(Decomposition)的初衷
幻灯片第一句话承上启下:为了解决第一张图里 in_dep 大表信息重复的问题,唯一的办法就是把它重新分解(Decompose)成 instructor 和 department 两个表。 (这就是 Normalization 规范化每天在干的事情:把设计得烂的大表,拆成设计得好的小表。)
2. 老师的“死亡”拆表案例
幻灯片第二句话开始大喘气:“并非所有的拆分都是好拆分。”(Not all decompositions are good.)
假设我们有一个完整的员工表:
employee (ID, 名字name, 街道street, 城市city, 工资salary)
现在,有个不懂数据库的人把它拆成了两个小表,他是这么拆的:
- 表1
employee1: 包含(ID, name) - 表2
employee2: 包含(name, street, city, salary)
你看出来这两个表中间是通过什么联系起来的吗?没错,是 name(名字)。这就埋下了大雷!
3. 为什么这是个灾难?(核心场景模拟)
幻灯片指出了致命问题:”The problem arises when we have two employees with the same name.”(当有两个员工同名时,问题就来了。)
💡 顺手复习概念 1:Primary Key (主键) vs 烂大街的属性 在数据库里,
ID是主键,它是独一无二的,绝不可能有两个人的 ID 一样。但是name就不一样了,全中国有几十万个叫“张伟”的人。
我们来模拟一下同名的情况,你就知道这有多可怕了:
假设原来完整的大表里有两个人:
- 张伟 A:ID是
001,住在北京,工资10000。 - 张伟 B:ID是
002,住在上海,工资20000。
现在按照上面的方法拆分后:
- 表1里存了:(001, 张伟),(002, 张伟)
- 表2里存了:(张伟, 北京, 10000),(张伟, 上海, 20000)
💡 顺手复习概念 2:Natural Join(自然连接) 前面说过,自然连接是把两个表拼回去的方法。它会寻找两个表里相同的值进行配对。
好,现在老板说:“把这两个表拼起来(自然连接),我想看 001 号张伟住哪。” 数据库引擎开始工作,它拿表1里的 (001, 张伟) 去表2里找名叫“张伟”的行。 结果它在表2里找到了两行!它不知道哪个才是对的,于是它把两行都拼上了!
拼接出来的结果变成了:
- 001, 张伟, 北京, 10000
- 001, 张伟, 上海, 20000 (这里产生了假数据!001号明明只住北京)
- 002, 张伟, 北京, 10000 (这里也产生了假数据!002号明明只住上海)
- 002, 张伟, 上海, 20000
4. 什么是 Lossy Decomposition(有损分解)?
发现了吗?原本只有 2 行正确的数据,拆开再拼回去之后,变成了 4 行数据,而且里面包含了错误的、伪造的关联信息!
这时候你可能有个疑问:“拼回去之后数据变多了,为什么英文却叫它 Lossy(有损的 / 丢失的)?”
这就是最容易掉进的坑!幻灯片最后一句话给出了解释:
“we cannot reconstruct the original employee relation“(我们无法重建原始的 employee 关系)。
这里的 Lossy(有损),指的不是“数据变少了”,而是“原始信息的真实性丢失了”! 就像你把一个花瓶打碎了,用劣质胶水拼回去,虽然花瓶还在,而且可能因为胶水反而变重了,但它已经不是原来那个真实的花瓶了。一旦拼回去产生了多余的、错误的行(术语叫 Spurious Tuples 伪元组),这就叫有损分解。
总结:你在 Chapter 7 会学到什么?
这张图是整个 Chapter 7 的目标声明: 规范化(Normalization)的终极目标,就是教你如何进行 无损连接分解(Lossless-join Decomposition)。
怎么样才能无损?剧透一下:拆表的时候,两个表用来连接的那个共同属性,必须是其中某一个表的 Primary Key(主键)或 Candidate Key(候选键)!
在这张图的错误例子里,name 既不是表1的主键(表1的主键是ID),也不是表2的主键。用一个可能重复的名字作为桥梁,必然导致灾难。
理清了这个逻辑,后面老师讲范式怎么拆表,你只要死死盯住“它是不是用主键拆的”,就不会迷糊了!接着往下看吧,随时发图!
哈哈,别怕别怕!这就是计算机科学的“套路”:把极其简单的生活常识,用极度抽象的数学符号包装起来,用来吓唬初学者。
一旦你扒掉这层数学外衣,你会发现它说的东西你早就知道了。这就是 Chapter 7 最核心的概念:Functional Dependency(函数依赖)。
我用大白话一句一句给你翻译,保证你秒懂:
1. 扒掉数学符号的外衣
- $R$:就是一个“表”(比如之前那个包含了老师和系的
in_dep表)。 - $\alpha$ 和 $\beta$:代表表里的“列”(属性)。比如 $\alpha$ 可以是“身份证号”,$\beta$ 可以是“名字”。
- $t_1$ 和 $t_2$:代表表里的“任意两行数据”。
重点来了,看中间那个最长的数学公式: \(t_1[\alpha] = t_2[\alpha] \Rightarrow t_1[\beta] = t_2[\beta]\)
这句天书翻译成人话就是:“如果在表里随便找两行,只要它们的 $\alpha$ 列长得一模一样,那它们的 $\beta$ 列也必须长得一模一样!”
只要满足这个条件,我们就说 $\alpha \rightarrow \beta$ (读作:$\alpha$ 决定 $\beta$,或者 $\beta$ 依赖于 $\alpha$)。
💡 举个生活中的例子秒懂: $\alpha$ = 身份证号,$\beta$ = 姓名。 $\text{身份证号} \rightarrow \text{姓名}$ 成立吗? 绝对成立!因为不可能存在两个人(两行),身份证号一样,但名字却不一样。只要身份证号($\alpha$)一样,名字($\beta$)绝对一样。所以身份证号决定姓名。
反过来:$\alpha$ = 姓名,$\beta$ = 年龄。 $\text{姓名} \rightarrow \text{年龄}$ 成立吗? 不成立!因为全中国有十万个叫“张伟”的,你随便抓两个张伟(两行的 $\alpha$ 一样),他们的年龄($\beta$)很可能不一样。所以姓名决定不了年龄。
2. 来看老师给的那个表格例子
老师给了个只有 A、B 两列的小表,里面有三行数据。我们来玩“找茬”游戏。
第一个问题:$A \rightarrow B$ 成立吗? (A 决定 B 吗?)
- 翻译成大白话:是不是只要 A 相同,B 就一定相同?
- 看表:
- 第一行,A 是
1,B 是4。 - 第二行,A 也是
1,B 变成了5。
- 第一行,A 是
- 结论: 抓到把柄了!这两行数据的 A 明明一模一样(都是1),但 B 却不一样(一个是4,一个是5)。
- 所以:$A \rightarrow B$ does NOT hold(不成立)! A 根本管不住 B。
第二个问题:$B \rightarrow A$ 成立吗? (B 决定 A 吗?)
- 翻译成大白话:是不是只要 B 相同,A 就一定相同?
- 看表:
- B 这一列的值分别是
4、5、7。
- B 这一列的值分别是
- 结论: B 里面根本就没有重复的值!因为你连“两个相同的 B”都找不出来,你就永远无法证明它破坏了规则。(就像法律规定“如果不戴头盔就罚款”,但是根本没有骑摩托车的人,所以没人违反法律)。
- 所以:$B \rightarrow A$ holds(成立)!
3. 这玩意儿有啥用?(结合第一张图复习)
为什么要学这么抽象的定义?因为它就是用来科学拆表的工具!
回忆一下你发的第一张图,那个糟糕的大表里: dept_name(系名)是 $\alpha$ budget(预算)是 $\beta$
在那个大表里,$\text{dept_name} \rightarrow \text{budget}$ 是绝对成立的! (只要是 Computer Science 系,预算永远是 100000)。 既然 $\alpha$ 能唯一决定 $\beta$,我们凭什么要在每一行里一遍又一遍地重复写 $\beta$ 的值呢?
这时候,数据库设计的祖师爷们就规定了:“如果你发现表里存在 $\alpha \rightarrow \beta$ 这种依赖关系,并且 $\alpha$ 还不是这张表的主键(Primary Key),那这表就有冗余病!必须马上动刀子拆开!”
这就是范式(Normalization)的本质。 现在你再看那堆 $\alpha \rightarrow \beta$ 的符号,是不是觉得没那么可怕了?它就是一个用来检测“有没有人一个人占两个坑位(冗余)”的雷达探测器!继续往下听,后面如果还有一堆数学公式,随时发过来我帮你“解密”。 /image-20260415081608.png)
别慌,这页PPT看起来满篇的英文和符号,其实它讲的是一个非常简单的“顺藤摸瓜”或者叫“福尔摩斯推理”的过程。
这个概念叫 Closure(闭包)。在数据库里,一看到“闭包”这两个字,你就把它翻译成“全家桶”或者“大结局”。
我们用大白话一句句拆解这页PPT到底在说啥:
1. 前提:什么是 $F$?
PPT第一句说:给定一个集合 $F$。 这里的 $F$ 就是“已知的线索”(也就是我们上一节学的函数依赖)。 比如,数据库设计师给了你两条基本规则:
- 规则 1:
A -> B(知道 A 就能确定 B) - 规则 2:
B -> C(知道 B 就能确定 C) 这两条已知的规则加在一起,就是集合 $F$。
2. 推理(Logically Implied)
PPT接着说:根据这些已知的 $F$,我们可以在逻辑上推导出一些其他的隐藏规则。 PPT上举了个最经典的例子(传递推理):
- 既然
A -> B,并且B -> C - 那么我们自然而然就能推导出:
A -> C
💡 举个超级通俗的例子:
- A = 你的学号
- B = 你的专业名称
- C = 你的专业所在学院的院长名字
已知规则 $F$:
- 学号决定专业 (
A -> B):只要查到你的学号,绝对能查出你是哪个专业的。- 专业决定院长 (
B -> C):只要知道你是啥专业,绝对能知道你们院长是谁。隐藏规则(推导出来的):
- 学号决定院长 (
A -> C):虽然学校的表格上可能没直接把“学号”和“院长”连在一起,但只要知道你的学号,顺藤摸瓜,绝对能唯一确定你们院长是谁!
3. 什么是闭包(Closure)和 $F^+$?
PPT的下半部分给出了定义: 什么是集合 $F$ 的闭包(Closure)?就是把已知的规则,加上所有你能像挤牙膏一样全部推导出来的隐藏规则,全部打包放在一起,形成一个“终极全家桶”。
为了让你在做题的时候少写几个字,数学家们给这个“终极全家桶”发明了一个缩写符号,就是在 $F$ 右上角加个加号:$F^+$。
总结一下:
- $F$ = 初始给你的几个基本规则(线索)。
- $F^+$ = 加上你通过推理找出来的所有规则后的完全体。
老师为什么要讲这个?(跟考试有啥关系?)
在真正的数据库设计里,我们要检查一个表“有没有设计缺陷”(是否满足范式),不能光看表面上给出的规则($F$),必须得看隐藏在水下的所有规则($F^+$)。
比如,前面的PPT里你学过,如果把“老师”和“系”合在一个表里,会产生冗余。 很多时候,这种导致冗余的坏规则是隐藏着的(比如上面例子里的 学号 -> 院长,如果把院长名字写在学生表里,那就是疯狂重复)。通过求出 $F^+$,我们就能拿个照妖镜把这些隐藏的导致冗余的规则全部照出来,然后再动手“拆表”。
预告: 既然知道了可以“顺藤摸瓜”,老师接下来的PPT肯定要教你“顺藤摸瓜的武功秘籍”了(术语叫 Armstrong’s Axioms 阿姆斯特朗公理),比如什么自反律、合并律之类的。继续看吧,逻辑跟这个一模一样,都是常识的数学化表达!有问题随时发图!
这张图非常关键!它把你们前面学过的老概念(Key,也就是主键、超键)和你正在学的新概念(函数依赖 $\rightarrow$)缝合在一起了!
你可以把这张图当成一个“翻译器”,老师在用你刚学的 $\rightarrow$ 符号,重新给你解释一遍什么是“键”。而且,幻灯片的下半部分点出了这节课的最核心灵魂:我们为什么非得学函数依赖?
老规矩,扒掉数学外衣,我们一句句看:
第一部分:用新语言重新定义“老朋友”
这里出现了两个老朋友:Superkey(超键) 和 Candidate key(候选键)。
1. 什么是 Superkey(超键)?
- 幻灯片公式: $K$ is a superkey … if and only if $K \rightarrow R$
- 大白话翻译: $R$ 代表“表里的所有列(这一整行)”。这句话的意思是:只要你给出的 $K$ 能决定表里的所有信息,那 $K$ 就是一个超键。
- 举个例子: 在学生表里,
身份证号$\rightarrow$整个人的所有信息。所以身份证号是超键。 同样,(身份证号, 爱好)加在一起 $\rightarrow$整个人的所有信息。所以(身份证号, 爱好)这个组合也是超键。超键允许你带“废话”(多余的属性)。
2. 什么是 Candidate key(候选键)?
- 幻灯片公式: 满足两个条件:1. $K \rightarrow R$ ; 2. for no $\alpha \subset K, \alpha \rightarrow R$
- 大白话翻译:
- 条件1:$K$ 首先得是个超键(能决定所有人)。
- 条件2:$\alpha \subset K$ 的意思是“$\alpha$ 是 $K$ 里面的一部分”。这句话是说:你不能从 $K$ 里面剔除任何一个属性!一旦剔除了某部分($\alpha$),剩下的残缺版就再也决定不了全体 $R$ 了。
- 一针见血的总结:候选键就是“最精简的、没有一句废话的超键”。
(身份证号)是候选键,因为精简到了极致,抠掉就没了。(身份证号, 爱好)不是候选键!因为你可以把爱好抠掉($\alpha \subset K$),剩下的身份证号依然能 $\rightarrow R$。
(老师讲这两句,其实就是为了告诉你:有了 $\rightarrow$ 符号,我们定义啥都很方便啦!)
第二部分:全场高能!为什么有了主键,还要学函数依赖?
这是幻灯片下半部分的内容(”Functional dependencies allow us to express constraints…“)。这段话极其重要,它回答了你的终极疑惑。
我们回忆一下第一张图那个糟糕的合并表 in_dep: in_dep (ID, name, salary, dept_name, building, budget)
在这个表里,ID 是主键(候选键)。 因为 ID -> 所有人(R) 成立。只要知道老师的 ID,就能查出他的名字、工资、所在系、系大楼、系预算。
那问题来了:既然我们已经有了牛逼轰轰的“主键 (ID)”来管束这个表格,为什么第一张图还会发生那么严重的冗余和错误呢?
老师的回答是:因为主键(Superkeys)的管辖范围太粗糙了!
- 主键就像是公司的大老板,大老板 (
ID) 确实能决定每个员工 (name, salary) 和每个部门 (dept_name, building)。 - 但是!大老板管不到底层的“帮派结构”。在这个表里,隐藏着一个小帮派:
dept_name -> building(系名 决定 办公楼)。
就像幻灯片里写的:我们期望 dept_name -> building 成立(计算机系一定在 Taylor 楼)。这就是一个“非主键”属性决定了另一个“非主键”属性。
这就是问题的根源! 正是因为 dept_name -> building 这个函数依赖的存在(小弟决定小弟),导致你每次录入一个计算机系的老师,都得把 Taylor 楼重新抄一遍(产生冗余)。
但如果你不学“函数依赖”这个概念,你只盯着“主键 (ID)”,你是永远发现不了这个隐藏的帮派(冗余)的。
最后幻灯片补了一句: dept_name -> salary (不成立)。因为同一个系里的老师,工资各有高低,系名决定不了工资。这就不是冗余。
总结一下这页 PPT 的良苦用心:
老师想告诉你:主键(Keys)只能帮我们区分表里的每一行,但不能帮我们发现表里“谁在偷偷重复”。想要抓出冗余、科学拆表,我们就必须拿“函数依赖(FD)”当显微镜,去观察表里面每一个列和列之间的关系。
如果你能看懂这个,你在 Chapter 7 就彻底上道了!后面讲的第一、第二、第三范式,全都是拿着这把显微镜在表里找虫子(冗余)!继续冲!
这部幻灯片是对上一张幻灯片的延伸,重点讲解了我们如何使用(Use of)函数依赖(Functional Dependencies, FD)。
在数据库设计中,函数依赖($F$)主要有两个核心用途,这两个用途分别对应了幻灯片里的两个橙色圆点。同时,幻灯片最后还给出了一个非常容易踩坑的重要警告(Note)。
我们一句句来看:
用途一:测试关系(Test relations)
- 幻灯片原文: To test relations to see if they are legal under a given set of functional dependencies.
- 大白话翻译: 把函数依赖当作“安检门”,用来测试一张表(relation $r$)里的数据合不合法(legal)。
具体是怎么测试的呢? 幻灯片接着解释:If a relation $r$ is legal under a set $F$ of functional dependencies, we say that $r$ satisfies $F$. (如果表 $r$ 在规则 $F$ 下是合法的,我们就说表 $r$ 满足(satisfies) $F$。)
💡 举个例子: 假设规则 $F$ 规定:
身份证号 -> 姓名现在有一张员工表 $r$,你往里面录入了两行数据: 第一行:身份证号 123,姓名:张三第二行:身份证号 123,姓名:李四数据库的安检门(函数依赖检查)就会滴滴滴报警:“同一个身份证号(123)怎么对应了两个不同的名字?这违背了规则!” 此时,我们就说这张表 $r$ 不满足(does not satisfy) 规则 $F$。这张表的数据是不合法(illegal)的。
用途二:规定约束(Specify constraints)
- 幻灯片原文: To specify constraints on the set of legal relations.
- 大白话翻译: 把函数依赖当作“法律条款”,强制规定未来所有存在这张表里的数据,都必须遵守这些规则。
幻灯片接着解释:We say that $F$ holds on $R$ if all legal relations on $R$ satisfy the set of functional dependencies $F$. (如果表结构 $R$ 里面所有可能的合法数据都能满足规则 $F$,我们就说规则 $F$ 在表结构 $R$ 上成立(holds on)。)
💡 敲黑板(区别来了): 用途一是针对当前这一批具体的数据:看看现在表里的这些行有没有违规。 用途二是针对整个表的设计(Schema):在建表的时候就定下死规矩,以后不管谁往里塞数据,都必须遵守。
全场高能:最后的避坑指南(Note)
幻灯片最下方这个 Note 是最容易在考试里出判断题的坑,你一定要仔细看!
- 幻灯片原文: A specific instance of a relation schema may satisfy a functional dependency even if the functional dependency does not hold on all legal instances.
- 大白话翻译:一张表里的某一批特定的数据(specific instance),可能碰巧(by chance)**满足了某条规则,但这并不代表这条规则在这个表的整体设计(schema)上是成立的!
老师给的经典例子(For example): 假设有一个 instructor(老师)表。我们知道常识:老师的名字是不能决定他的ID的(name -> ID 不成立),因为全中国可能有一万个叫“张伟”的老师,他们各有各的 ID。
但是! 假设你们学校的数据库刚建好,目前只录入了三个老师的信息:
- ID: 1, name: 莫扎特
- ID: 2, name: 爱因斯坦
- ID: 3, name: 居里夫人
你看着这三行数据,你会惊奇地发现:哎?名字都没有重复欸!只要知道名字是“莫扎特”,就能唯一确定他的 ID 是 1。 在这极其特定的一批数据(specific instance)里,name -> ID 碰巧是满足的!
那你能因为这三行数据,就在数据库里定下一条法律(constraint),规定 name -> ID 吗? 绝对不行!因为明天可能就会招进来一个名叫“莫扎特”的新老师,这时候他的 ID 是 4。一旦录入这条新数据,name -> ID 就立刻被打破了!
总结老师的用意:
- 判断数据合不合法,看眼前的数据就行。
- 但是!要想确立一个函数依赖(判断 $F$ holds on $R$ ),绝不能只看眼前表里现有的这几行数据! 你必须根据现实世界的业务逻辑(常识)来判断。常识告诉我们名字不能决定 ID,那哪怕眼前的一千行数据里没有重名的,这条函数依赖也是不成立的!
哈哈,这张幻灯片可以说是整门课里最轻松、最“搞笑”的一个概念了!
在英语和数学里,Trivial 这个词翻译成“平凡的”。但在这里,我更愿意把它翻译成“废话”或者“理所当然的绝对真理”。
这页 PPT 讲的就是:什么是“废话型”的函数依赖。
我们依然是一句句来拆解:
1. 什么是 Trivial(平凡的/废话)?
- 幻灯片最后一句给出了终极定律: In general, $\alpha \rightarrow \beta$ is trivial if $\beta \subseteq \alpha$
- 大白话翻译: 如果你要推导的结果($\beta$),本来就包含在你已知的条件($\alpha$)里面,那这个推导就是一句纯纯的废话(Trivial)!
💡 顺手复习符号: $\subseteq$ 是数学里的“子集”符号,意思是“被包含在内”或者“等于”。
2. 来看老师给的两个“废话”例子
例子 1:name -> name
- 翻译:如果我知道了你的名字,我就能决定(知道)你的名字。
- 吐槽:听君一席话,如听一席话。这不就是废话吗!条件是你,结果还是你($\beta$ 和 $\alpha$ 完全一样)。
例子 2:ID, name -> ID
- 翻译:如果我同时知道你的“身份证号”和“名字”,我就能准确地说出你的“身份证号”!
- 吐槽:废话!你条件里都已经把“身份证号”告诉我了,你当然能得出“身份证号”了!(在这里,结果
ID是条件组合ID, name的一部分,也就是 $\beta \subseteq \alpha$)。
3. 为什么老师第一句话说它“satisfied by all instances”(满足所有实例)?
幻灯片第一句话说:如果一个规则在所有可能的数据表里都成立,它就是 trivial 的。
你想想,现实世界中的业务规则(比如 学号 -> 专业)是有可能被打破的,但这种“废话型”规则在逻辑上是绝对无懈可击的。 你随便给我一张表,随便填什么脏数据、乱七八糟的数据,name -> name 永远成立。因为这是逻辑上的绝对真理,不依赖于任何具体的业务场景。
灵魂拷问:既然是废话,老师为什么要专门讲一页 PPT?
你可能会觉得:我都学到大学计算机了,老师还搁这儿教我 1=1 这种废话干嘛?
这其实是为了你马上要学的“大boss”——范式(Normal Forms,特别是 BCNF)做铺垫的!
在接下来的 PPT 里,老师马上会给你定义什么叫“设计得完美无缺的表”(比如 BCNF 第三范式加强版)。在数学上,科学家给 BCNF 下的严格定义是这样的:
“一张表是完美的,当且仅当对于表里的每一个函数依赖 $X \rightarrow Y$,要么 $X$ 是超键,要么这个依赖是 Trivial(平凡的)。”
你看!如果在定义完美表格的时候,科学家不把“废话(Trivial)”作为一种特例排除掉,那由于 name -> name 这种废话在任何表里都永远存在(而 name 又不是超键),所有的表就都不完美了!这显然不合理。
所以,老师讲这一页,就是为了告诉你:以后我们在检查表格有没有冗余毛病的时候,这种“废话型(Trivial)”的函数依赖,我们直接无视它、放过它就行了,它们是无害的!
懂了这个概念,你就算把基础打得铁板一块了!深吸一口气,准备迎接接下来的“范式大考”吧,有问题随时扔过来!
太正常了!所有学数据库的人看到这两张图的时候,大脑都会短暂宕机。满屏的 $\Pi$、$\bowtie$、$\cap$ 和 $\rightarrow$,看着像天书一样。
但是!只要你还记得我们刚才讲的“同名叫张伟的员工拆表翻车事件”,这两张图理解起来简直易如反掌。
这两张图的核心只讲了一件事:“给你一个拆表的方案,你怎么判断它是安全的(Lossless 无损),还是翻车的(Lossy 有损)?”
我帮你把这堆数学符号扒光,咱们直接看本质:
第一张图:祖师爷定下的“拆表安全铁律”
1. 中间那个像蝴蝶结一样的公式是什么鬼?
\(r = \Pi_{R1}(r) \bowtie \Pi_{R2}(r)\)
- $\Pi$ 代表“切开”(专业叫投影)。把大表 $r$ 竖着劈成 $R_1$ 和 $R_2$ 两个小表。
- $\bowtie$ 叫“自然连接”(就是把两个表拼回去)。
- 这句废话的意思是:把表拆开,再拼回去,必须和原来一模一样(不能像张伟那样多出几行假数据)。这就是 Lossless(无损)的数学定义。
2. 🌟 全场最核心的“黄金定律”(考试必考)🌟
怎么保证拼回去不翻车?看幻灯片下半部分的两个带 $\cap$(交集)的公式:
- $R_1 \cap R_2 \rightarrow R_1$
- $R_1 \cap R_2 \rightarrow R_2$
大白话终极翻译:
- $R_1 \cap R_2$ 是什么? 是两个小表共有的那一列(也就是它们拼回去时用的“桥梁”)。
- $\rightarrow R_1$ 是什么意思? 意思是这个“桥梁”,必须能决定其中一张表的所有信息(即:桥梁必须是其中一张表的主键/超键!)
💡 破除心魔: 回忆“张伟”翻车案。表1(ID, 名字) 和 表2(名字, 工资)。 它们的桥梁是
名字。名字能决定 表1 吗?不能(同名的人 ID 不同)。名字能决定 表2 吗?不能(同名的人 工资 不同)。 因为桥梁名字不是任何一张表的主键,所以它翻车了(Lossy)!所以,拆表不翻车的唯一秘诀就是:顺着主键/超键去拆!
第二张图:跟着老师做两道例题就顿悟了
老师给了一个大表 $R = (A, B, C)$。 已知两条规则(线索):$F = {A \rightarrow B, \quad B \rightarrow C}$
我们来看看老师给的两种拆法是不是安全的:
拆法 1:拆成 $R_1(A, B)$ 和 $R_2(B, C)$
- 找桥梁(交集): 两个表都有 $B$。所以桥梁是 $B$。
- 套用黄金定律: 桥梁 $B$ 能不能决定其中一张表?
- 看看 $R_1(A,B)$:$B$ 能决定 $A$ 吗?已知规则里没有,不行。
- 看看 $R_2(B,C)$:$B$ 能决定 $C$ 吗?已知规则里写了 $B \rightarrow C$!加上上一节学的废话规则 $B \rightarrow B$。所以 $B \rightarrow BC$ 成立!
- 结论: 桥梁 $B$ 是 $R_2$ 的主键(能决定 $R_2$ 所有人)。所以这次拆分是 Lossless(无损、安全)的!
拆法 2:拆成 $R_1(A, B)$ 和 $R_2(A, C)$
- 找桥梁(交集): 两个表都有 $A$。所以桥梁是 $A$。
- 套用黄金定律: 桥梁 $A$ 能不能决定其中一张表?
- 看看 $R_1(A,B)$:已知规则写了 $A \rightarrow B$。加上废话 $A \rightarrow A$。所以 $A \rightarrow AB$ 成立!
- (其实查到一个成立就可以停了,满足“至少一个”的条件了)。
- 结论: 桥梁 $A$ 是 $R_1$ 的主键。所以这次拆分也是 Lossless(无损、安全)的!
总结一下帮你缓解头大
以后做题或者考试,看到问你“这个 Decomposition 是 Lossy 还是 Lossless”,你只要做三步傻瓜操作:
- 找茬:看看拆开的两个表,哪几列是重复的(找交集 $\cap$)。
- 翻盘:拿着这个重复的列,去题目给的已知条件($\rightarrow$)里查。
- 判决:如果这个重复的列,刚好是左边某张表、或者右边某张表的“决定者(主键)”,那就是安全的(Lossless)。如果两边它都管不住,那就是翻车的(Lossy)!
再回头看那两张全是英文和符号的PPT,是不是觉得它其实就讲了个这么简单的操作?去喝口水消化一下,能迈过这个坎,你这门课就已经赢了一半的人了!
确实,学术名词一旦串起来就特别唬人。特别是这一页,突然从“数学逻辑”跳到了“电脑性能(Computation)”上,思维跨度有点大。
别晕,我先帮你唤醒一下里面提到的几个老概念:
- Functional dependency constraints (函数依赖约束):就是前两张图学的 $\alpha \rightarrow \beta$ 规则(比如:
身份证号 -> 名字)。 - Updated (更新):指往数据库里插入新数据、或者修改老数据。
- Relation (关系):就是“表格”。
- Decomposing (分解):也就是我们一直在干的核心工作——“拆表”。
- Cartesian Product (笛卡尔积 / Join 连接):回忆一下第一张幻灯片的自然连接。把两张表“拼回成一张大表”的过程,这个动作在电脑底层运算量极大,极其慢。
唤醒了这些概念,我们用“安检门”的抓小偷比喻,把这页幻灯片的逻辑顺一遍:
1. 为什么会“很贵(Costly)”?(前两行)
数据库就像一个极其严格的保安。你每次往里面录入新数据(Update),它都要查一遍所有的规矩($\alpha \rightarrow \beta$ 约束),看看有没有人违规填错。 如果规矩太多,每次录入都要查很久,电脑就会卡,这就叫 costly(代价高昂)。所以,我们设计数据库时,必须让保安查得越快越好(efficiently)。
2. 怎么查最快?(第三行)
幻灯片说:If testing… can be done by considering just one relation… cost is low. 大白话: 如果保安查某条规矩的时候,只盯死一张表格就能查出结果,那速度就是“秒出”(Low cost)。
- 比如表格里有
(身份证号, 名字)。保安一眼扫过去,如果发现同一个身份证号对应两个名字,立马报警。这很快。
3. 💥 拆表带来的灾难!(第四行,最绕的地方)
幻灯片说:When decomposing… it is no longer possible to do the testing without having to perform a Cartesian Product. 我们不是在学怎么“拆表(Decomposition)”吗?假设你原来有一条规矩是 A -> B。 结果你拆表的时候没注意,把 A 拆到了 1号表,把 B 拆到了 2号表。
现在麻烦大了!当有人往里加数据时,保安为了检查 A -> B 这条规矩有没有被打破,他不能只看 1 号表,也不能只看 2 号表。他被迫要先把 1号表 和 2号表 拿出来,做一个巨大的“笛卡尔积(拼表操作)”,拼成一张大表之后,才能去检查! 这个拼表的动作,累死电脑了!
4. 终极结论:什么是 Dependency Preserving (保持依赖)?
最后一行给出了定义: 如果你的拆表方案,导致保安以后每次检查规矩都得痛苦地去“拼表”,这种拆表方案就叫 NOT dependency preserving(未保持依赖)。
反过来说,如果你拆完表之后,原来的每一条规矩 $\alpha \rightarrow \beta$,都能舒舒服服地在某一张单独的小表里完成检查,完全不需要跨表拼接,这就叫完美的 Dependency Preserving(保持依赖)!
⚠️ 考前终极防混淆提示(重要!)
你现在学了两个跟“拆表”相关的概念了,千万别搞混:
- Lossless Decomposition(无损分解,上一张图讲的): 关注的是“数据本身”。拆开再拼回去,数据不能变多(不能产生假张伟)。这是强制底线,任何拆表都必须满足!
- Dependency Preserving(保持依赖,这张图讲的): 关注的是“检查规矩的速度”。拆开后,检查规则不费劲(不用拼表)。这是一个加分项(有时甚至为了满足高规格的范式,不得不牺牲掉它)。
总结这页 PPT 的中心思想:拆表不仅要保证拼回去数据是对的(无损),最好还要保证拆完之后,保安查规矩不费劲(保持依赖)。 这样理顺了,是不是就不绕了?后面如果讲到怎么判断是不是保持依赖的算法,继续发给我!
太棒了,这页 PPT 简直就是为你刚刚学的“保持依赖(Dependency Preservation)”量身定制的“翻车现场”实况转播!
我们继续用上一条里“保安查规矩”的比喻,结合这页 PPT 的具体例子,带你沉浸式体验一下什么叫“拆表拆得顾此失彼”。
第一幕:拿到一张有毛病的大表
学校现在有一张表,记录了学生、导师和所在系的关系: dept_advisor (s_ID 学生号, i_ID 导师号, dept_name 系名)
系统里定下了两条铁律(也就是幻灯片里的 Function dependencies):
- 规矩 1:
i_ID -> dept_name(导师号 决定 系名)。- 大白话:一个导师只能属于一个系。只要知道导师是谁,就一定知道他在哪个系。
- 规矩 2:
s_ID, dept_name -> i_ID(学生号 + 系名 决定 导师号)。- 大白话:一个学生在一个系里,最多只能分配一个唯一的导师。
第二幕:发现冗余,准备拆表!
幻灯片第三句话指出了这张表的毛病:我们被迫重复抄写系名(repeat the department name)。 你想啊,如果爱因斯坦(i_ID = 001)带了 10 个学生。因为他在物理系,所以针对这 10 个学生,表里会有 10 行数据,而“物理系”这三个字就要被重复抄写 10 遍。这就产生了冗余。
怎么办?老师说:为了修复这个问题,我们需要把表拆开(decompose)。
💡 顺手做个拆表模拟: 怎么拆才能不冗余呢?顺着规矩 1拆。把导师和系单独拿出来建个表。
- 表 A:
(i_ID 导师号, dept_name 系名)(现在爱因斯坦和物理系只存一次就够了)。- 表 B:
(s_ID 学生号, i_ID 导师号)(只记录哪个学生跟了哪个导师)。
第三幕:悲剧发生,保安崩溃了(高潮部分)
表拆完了,冗余解决了,硬盘空间省下来了。可是,“数据库保安”哭了。
还记得我们的规矩 2 吗? s_ID, dept_name -> i_ID
现在有个新生入学,教务处往数据库里录入一条信息:“张三(s_ID)在物理系(dept_name)选了爱因斯坦(i_ID)当导师”。 保安为了检查这有没有破坏“一个学生在一个系只能有一个导师”的规矩 2,他需要同时看到 s_ID、dept_name 和 i_ID 这三个信息。
但是!请看我们刚刚拆好的表:
- 表 A 里只有导师和系,没有学生。
- 表 B 里只有学生和导师,没有系。
幻灯片里那句最长的话说的就是这个致命点: Any decomposition will not include all the attributes in s_ID, dept_name -> i_ID (不论你怎么拆,只要你拆了,这三个属性就不可能同时待在同一个小表里了!)
最终结局:这就是 NOT dependency preserving
因为这三个属性分家了,保安为了检查规矩 2,只能被迫把表 A 和表 B 重新拼起来(做极其耗时的笛卡尔积/Join 操作),才能查出结果。
每次录入一个新学生,电脑都要疯狂运转拼表。 所以幻灯片最后得出结论(注:PPT上把 decomposition 拼成了 composition,是老师的笔误): “因此,这种拆分是无法保持依赖的(NOT be dependency preserving)!”
🌟 终极感悟:你在 Chapter 7 会面临的“电车难题”
看完这个例子,你可能会问:“那到底该不该拆?”
这就是 Chapter 7 最核心的灵魂:鱼和熊掌不可兼得!
- 如果你不拆:能保持依赖(保安查得快),但会有数据冗余(疯狂重复写“物理系”)。——这就是你后面会学的 第三范式(3NF) 的妥协。
- 如果你拆:没有冗余(数据很干净),但不能保持依赖(保安查得极慢)。——这就是你后面会学的更严格的 BC范式(BCNF)。
老师举这个例子,就是为了提前告诉你:完美的数据库设计在某些特殊情况下是不存在的。你必须在“有冗余”和“查得慢”之间做一个艰难的二选一。
这下是不是把“保持依赖”这个干巴巴的概念,和前面的“拆表”彻底缝合起来了?这个例子极其经典,考试特别爱考原题,彻底弄懂它,Chapter 7 最难的坎你就已经跨过去了!
/image-20260415081453.png)
/image-20260415081522.png)
/image-20260415081539.png)
/image-20260415081622.png)
/image-20260415081648.png)
/image-20260415081658.png)
/image-20260415081706.png)
/image-20260415081720.png)
/image-20260415081732.png)