/knowledge/notes/functional-dependencies
概念笔记 · 数据库系统
函数依赖
依赖说明哪些列决定哪些列。键、闭包和范式,都来自认真对待这些承诺。
- 学于
- 数据库系统INFO20003 · COMP90050
- 时间
- 2020 年第一学期
- 应用于
- Louvre Ops DB
- 阅读 / 复习
- 约 6 分钟阅读2026-10-21
函数依赖是对数据的一个承诺:只要两行在这几列上相同,它们在那一列上也相同。数据库设计有很大一部分工作,就是把这些承诺写下来,再安排表的结构,让每个事实只存在一个地方。这篇笔记讲一个依赖说的是什么,怎样从几个依赖推出它们蕴含的全部依赖,以及为什么无视依赖的表会让每次更新都有机会自相矛盾。
01
基本想法
当知道列 的值就能确定列 的值时,写作 ,读作“ 决定 ”。在一张售票表里,票号决定票种,票种决定票价。依赖是对系统任何时候可能存的每一张表的断言,而不只是今天表里那些行。所以它是一条业务规则,来自这些列的含义。
键是能决定所有其他列、又没有多余列的一组列。声明主键,就是声明一个从键到整行的依赖。这也是为什么一个随手选的键,会悄悄地让表存不下实际发生的事情。
麻烦始于依赖的左边不是键。想象一张很宽的展馆扫描表,每一行都抄着票种和票价。成人票价就出现在每一条成人票扫描记录里。改票价时你得改所有这些行,漏掉一行,表里就同时有两个成人票价。这叫更新异常。你也没法在有人用新票种扫码之前记录它的票价,而删掉最后一条会员票扫描记录,就忘了会员票卖多少钱。规范化通过拆表一次解决这三个问题,让每个依赖的左边成为它自己那张表的键。数据库系统主题会一步步讲把这件事精确化的各个范式。
02
数学
形式上,如果一个关系里没有两行在 上相同却在 上不同,就说它满足 。
一对行就足以推翻一个依赖。多少行都不足以证明一个依赖。
从一组声明的依赖 出发,Armstrong 的三条规则能推出所有必然成立的依赖:自反律、增广律和传递律。
这些规则是可靠的,不会推出错误的依赖,也是完备的,能推出每一个正确的依赖。
实际上很少有人手工套这些规则,而是计算闭包:从 开始,只要某个声明的依赖的左边落在当前集合里,就把它的右边加进来,直到不再变化。
键就是去掉任何一列都不再是超键的超键。
Boyce–Codd 范式要求每个成立的依赖左边都是超键。第三范式稍微放宽,允许右边是某个键的一部分,这样在 BCNF 做不到的时候,每个依赖仍然能在单张表里检查。
平凡指 Y 已经包含在 X 里,比如票号和时间决定时间。
拆表不能丢信息。把拆出来的表再连接回去,必须正好得到原来的行,不多也不少。当共享的列是其中一张表的键时,这一点就有保证。
按依赖 X 到 Y 拆分:用 X 和 Y 组成一张表,另一张表保留 X。
03
动手试
下面的工具存着一家虚构博物馆的展馆扫描日志,每次扫描一行,还有设计者声明的四条规则。用按钮拼出一个依赖,工具会分别回答两个问题:这份样本有没有违反它,它能不能从声明的规则推出来。
设计者声明的规则
- 票号 → 票种
- 票种 → 票价
- 展馆 → 楼层
- 票号, 时间 → 展馆
左边 X(选一个或多个)
票号 → 展馆
在这份样本里
不成立。第 1 行和第 4 行的票号相同(T08),展馆却不同:East 对 West。
从声明的规则推出
{票号}⁺ = {票号、票种、票价}。 展馆不在闭包里,所以规则没有承诺这一点。 左边不是键。
推导:票号 → 票种,再 票种 → 票价。
| # | 票号 (X) | 票种 | 票价 | 展馆 (Y) | 楼层 | 时间 |
|---|---|---|---|---|---|---|
| 1冲突 | T08 | Member | 0 | East | L2 | 09:10 |
| 2 | T03 | Adult | 22 | East | L2 | 09:14 |
| 3 | T01 | Adult | 22 | West | L0 | 09:17 |
| 4冲突 | T08 | Member | 0 | West | L0 | 09:18 |
| 5 | T01 | Adult | 22 | East | L2 | 09:57 |
| 6 | T08 | Member | 0 | North | L1 | 09:59 |
| 7 | T01 | Adult | 22 | West | L0 | 10:02 |
| 8 | T02 | Adult | 22 | West | L0 | 10:25 |
| 9 | T05 | Adult | 22 | West | L0 | 10:41 |
| 10 | T04 | Member | 0 | West | L0 | 10:53 |
| 11 | T02 | Adult | 22 | West | L0 | 11:08 |
| 12 | T04 | Member | 0 | East | L2 | 11:12 |
| 13 | T04 | Member | 0 | North | L1 | 11:54 |
| 14 | T06 | Adult | 22 | East | L2 | 12:10 |
| 15 | T07 | Adult | 22 | North | L1 | 12:38 |
| 16 | T06 | Adult | 22 | West | L0 | 12:43 |
不成立。第 1 行和第 4 行的票号相同(T08),展馆却不同:East 对 West。展馆不在闭包里,所以规则没有承诺这一点。
- 默认问的是票号能不能决定展馆。数据违反了它,因为一张票会去好几个展馆,而票号的闭包只有票号、票种和票价。
- 在左边加上时间。票号和时间一起决定展馆,闭包覆盖了所有列,这一对就是键。
- 试试票价决定票种。16 行时它成立。把行数拖到 24,会发现一张儿童票和一张会员票都是 0 元。票种决定票价,反过来从来不是规则。
- 试试时间决定票号。32 行以内,每次扫描都落在不同的分钟上,样本推翻不了它。到 40 行时,好几个分钟出现了重复。样本可以推翻依赖,却永远证明不了依赖。
- 勾上拆分。有三条声明的规则左边不是键,按它们拆分后存的单元格更少,成人票价也只存在一行里。
04
我在哪用到它
05
容易出错的地方
06
参考资料
- A relational model of data for large shared data banksCodd,Communications of the ACM 第 13 卷第 6 期,1970,第 377–387 页。开创关系数据库的论文,其中第一次讨论了关系的规范化。
- A simple guide to five normal forms in relational database theoryKent,Communications of the ACM 第 26 卷第 2 期,1983,第 120–125 页。短小直白,每个范式一个例子。至今仍是看懂每个范式禁止什么的最快途径。
- Database Management SystemsRamakrishnan 与 Gehrke,第 3 版,McGraw-Hill,2003。第 19 章。讲模式求精的标准教材章节:闭包、键、BCNF 和 3NF,以及无损且保持依赖的分解。