仿佛回到了被离散数学支配的日子。来一步步揭开数据库函数依赖的面纱吧!
首先给出一个问题,如何避免数据库的一致性问题?
数据库的规范性设计需要分析数据库Table中的属性在取值方面有什么依存关系?数据库设计过程中应遵循什么样的原则?
如何避免一致性问题?关于这个问题那我想问,什么是一致性问题?
当然,一般的课本或者老师,只会问你如何避免一致性问题,却从来不会告诉你什么叫一致性问题,也许他们自己也不懂。来看下百度百科的定义:
数据库一致性(Database Consistency)是指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。保证数据库一致性是指当事务完成时,必须使所有数据都具有一致的状态。
Oh my god! 这啥呀,啥又叫一致性状态?这个解释似乎不是我们想要的。有两种方案,
所以说,有些问题本身就是个问题,问题的解释还是个问题(禁止套娃)。这种抽象的问题交给数学家们去做吧,我们考虑点实在的东西。
首先数据库的设计理论有:
然后数据依赖一般分为函数依赖、多值依赖和连接依赖。其中函数依赖是最重要的数据依赖。下面来讨论下 函数依赖。
设 R ( U ) R(U) R(U) 是属性集合 U = { A 1 , A 2 , . . . , A n } U = \{A1,A2,...,An\} U={ A1,A2,...,An} 上的一个关系模式, X X X, Y Y Y 是 U U U 上的两个子集,若对 R ( U ) R(U) R(U) 的任意一个可能的关系 r r r, r r r 中不可能有两个元组满足在 X X X 中的属性值相等而在Y中的属性值不等,则称 “ X 函 数 决 定 Y ” “X函数决定Y” “X函数决定Y” 或 “ Y 函 数 依 赖 于 X ” “Y函数依赖于X” “Y函数依赖于X”, 记作 X → Y X \rightarrow Y X→Y。
这里注意下,"函数决定"和"函数依赖"是两个术语,不是说 X X X和 Y Y Y是函数
看起来还挺抽象的,那先举个例子来说明下吧:
比如 U = { 学 号 , 姓 名 , 手 机 , 班 级 , 年 龄 } U = \{ 学号, 姓名, 手机, 班级, 年龄\} U={ 学号,姓名,手机,班级,年龄},在这里,学号能唯一确定一个学生,所以有 { 学 号 } → { 姓 名 , 手 机 , 班 级 , 年 龄 } \{学号\} \rightarrow \{姓名, 手机, 班级,年龄\} { 学号}→{ 姓名,手机,班级,年龄},也就是说我们认为学号可以决定后面的这些属性。
根据定义,不存在两个元组在 X 中属性相同而在 Y 中属性不等,上面的举例完全符合。不过上述例子中的 X 是学号,学号是唯一的,因此学生表不可能有两个元组学号是相同的。换句话说,这个例子中的函数依赖太显然了,肯定是成立的,不足以把定义个的所有约束都给逼出来。
那我们就让它存在两个元组在 X 中的属性相同,然后去看看在 Y 中是否不同。很显然 { 姓 名 } → { 学 号 } \{姓名\} \rightarrow \{学号\} { 姓名}→{ 学号} 就是不成立的,因为只要有同名的同学,那么就存在两个元组满足在 { 姓 名 } \{姓名\} { 姓名} 中属性相同,在 { 学 号 } \{学号\} { 学号} 中属性不同。
当然,如果限制不能重名,那结果又是另一个了,所以要结合具体的业务逻辑。
说白了,函数依赖讨论的就是一组属性能否确定另一组属性这么个话题。
而函数则可以看成是某几个列。告诉你几列字段(比如 {学号}),再告诉你另外几列字段(比如 {姓名, 手机}),问你如果知道了前者能否唯一确定后者。至于这玩意儿为啥叫函数依赖,似乎还是不太明白,这里暂时先不管它了,先按上面的说法理解着,或许到最后就柳暗花明了。
先来说说非平凡的函数依赖,这个比较简单:
如果 X → Y X \rightarrow Y X→Y 但是 Y ⊄ X Y \not\subset X Y⊂X,则称 X → Y X \rightarrow Y X→Y 是非平凡的函数依赖。
比如,{部门号, 楼层号, 座位号} → \rightarrow → {座位号},后者是前者的子集,这就是不是非平凡的了,而且这也没啥使用意义。
其实,很多这种东西,自己可能会觉得莫名其妙,比如上面的举例,这不是明显重复了?谁会这么傻?是不是感觉很多东西都是很显然?比如,一个学生需要有名字,学号。这个还要想吗?但是当工作量大的时候,出现错误的概率就很大了,比如忘记加外键了,忘记加唯一约束了,等等。而且很多显然的东西未必就能想到哦。
这里扯多了,再来看下函数依赖的其他特性吧:
关于第5点,还记得前面提到的学号的例子吗?前面说过这种情况,学号是主键,唯一能确定一个学生。一个学号也就只有一行记录(即一个元组),当然不存在学号相同但是其他属性不同的两个元组喽。
这里着重要说的是第4点中的模式 R R R,还有某一关系 r r r,顺便回顾下基础概念。
数据库结构抽象中提到过三级模式和两层映像。我的另一篇博客记录过相关的概念:数据库系统的结构抽象
三级模式中的第二级—— 概念模式 又称为 模式、逻辑模式、全局模式。
所以这里的模式 R R R 其实就是指概念模式。我们是关系型数据库,所以这个模式就是我们的关系表的结构。而所谓的某个关系 r r r 呢,就是在这种模式下的某一行具体的数据。
有朋友可能会问,为啥一行数据就是一个关系呀?关系是笛卡尔积的子集,随便找两个笛卡尔积的子集写出来,结果无非就是一堆元组,类似 这种。我们这个具体的一行数据也是一个元组呀。比如 <学号, 姓名, 班级> 就是一个模式(假如不考虑约束),假设记作 R R R,那 <“10086”, “张三”, “高三7班”>就是这个模式下的一个具体的关系 r r r 了。所以第4点想说的无非就是这种函数依赖是全局成立还是局部成立的问题。这里的话顺带复习下前面的东西了。
学生(学号, 姓名,班级,课号, 课程名,成绩,教师,教师职务)
函数依赖举例:
思考一下:{学号, 课号} → \rightarrow → 教师成立吗?
我相信大部分人都会觉得成立,因为感觉起来确实是这样。然而,如果某个学生的某个课程不只一个老师在教呢?这时候就不成立了。这里要结合具体的业务逻辑进行分析。这也再次说明了不要凭感觉认为对不对,很显然的不一定是对的。
思考到这里,大体上也明白了函数依赖到底怎么样的。下面进一步看下
完全函数依赖和传递函数依赖。
在 R(U) 中,若 X → Y X \rightarrow Y X→Y 并且对于 X 的任何 真子集 X ′ X' X′ 都有 X ′ ↛ Y X' \nrightarrow Y X′↛Y,
则称 Y Y Y 完全函数依赖于 X X X,记作 X ⟶ f Y X \stackrel{f}{\longrightarrow} Y X⟶fY。如果 X X X 的真子集也能函数决定 Y Y Y,
则称 Y Y Y 部分函数依赖于 X X X,记作: X ⟶ p Y X \stackrel{p}{\longrightarrow} Y X⟶pY。
根据定义,我们可以知道完全函数依赖更严格,少了一个都不行。
举个的例子:
函数依赖表示为 X → Y X \rightarrow Y X→Y,如果 Y 就是 U(包含所有属性),又会发生什么事情呢?
这时候,不就是在说 X 能决定整条记录了吗?如果 Y Y Y 又是完全函数依赖 X X X 的,这里意味着 X X X 就是 候选键 了。使用过数据库的朋友应该知道这个,我们可以选择任意一个候选键作为 主键(Primary Key)。包含在任一候选键中的属性称 主属性(Prime Attribute),其他属性称 非主属性。这个在后面还会提到。
概念容易看懂,但需要注意它的条件。
定义:在R(U)中,若 X → Y X \rightarrow Y X→Y, Y → Z Y \rightarrow Z Y→Z 且 Y ⊄ X Y \not\subset X Y⊂X, Z ⊄ Y Z \not\subset Y Z⊂Y, Z ⊄ X Z \not\subset X Z⊂X, Y ↛ X Y \nrightarrow X Y↛X,则 Z Z Z 传递函数依赖于 X X X。
看懂这个并不难,但是有没有想过“且”字后面的条件到底是在限制啥呢?
还记得数理逻辑/离散数学中关系的传递性吗?
对任意a,b,c,已知满足关系 ,< b, c>,
如果< a, c>,则说明这个关系是传递的。
传递函数依赖中传递的体现和这个十分类似。我们有了 X → Y X \rightarrow Y X→Y 和 Y → Z Y \rightarrow Z Y→Z。那怎么样才能让 X → Z X \rightarrow Z X→Z呢?
我认为答案就定义中条件的后半部分:
Y ⊄ X Y \not\subset X Y⊂X, Z ⊄ Y Z \not\subset Y Z⊂Y, Z ⊄ X Z \not\subset X Z⊂X, Y ↛ X Y \nrightarrow X Y↛X,只要再满足这句话就成立了。Why?
按照离散数学(或者说数理逻辑)的一般思路,先证明必要性,然后证明充分性。但是为了节省时间,这里就不加以证明了,毕竟不是搞理论研究的。不过估计我也证不出来。
设 K 为 R(U) 中的属性或属性组合,若 K ⟶ f u K \stackrel{f}{\longrightarrow} u K⟶fu, 则称 k 为 r(u) 上的 候选键(candidate key)。
若 R(U) 中的属性或属性组合X并非R的候选键,但 X 却是另一关系的候选键,则称X为R的外来键(Foreign Key),简称 外键。
比如成绩表的候选键是成绩单号,有一个属性是学号,学号不是候选键,但是学号是学生表的候选键,所以学号是成绩表中的一个外键。
设 F 是关系模式 R ( U ) R(U) R(U) 中的一个函数依赖集合, X , Y X, Y X,Y 是 R R R 的属性子集,如果从 F F F 中的函数依赖能够推导出 X → Y X \rightarrow Y X→Y,则称 F 逻辑蕴涵 X → Y X \rightarrow Y X→Y, 或称 X → Y X \rightarrow Y X→Y 是 F F F 的 逻辑蕴涵。记作 F ⊨ X → Y F \vDash X \rightarrow Y F⊨X→Y。
换一种说法就是,设 F 是关系模式 R(U) 的函数依赖集,
X → Y X \rightarrow Y X→Y 是一个函数依赖,若对 R 中的每个满足 F 的关系 r,
能够用形式逻辑推理的方法推出r也满足 X → Y X \rightarrow Y X→Y,则称 F ⊨ X → Y F \vDash X \rightarrow Y F⊨X→Y。
或者换个更简单的说法,如果满足 F 的每个关系均满足 X → Y X \rightarrow Y X→Y,
则说 F 逻辑蕴涵 X → Y X \rightarrow Y X→Y
直白点讲就是给了一个集合 F,这个集合里面装的是一堆的函数依赖,如果我根据这里面装的函数依赖能推导出函数依赖 X → Y X \rightarrow Y X→Y,就说我们这个集合 F 蕴含了 X → Y X \rightarrow Y X→Y.
理解这个之后再回顾下数理逻辑中的蕴含
蕴含 是个数理逻辑的概念。设p、q为两个命题。复合命题"若p,则q"称为p与q的蕴含式,记作 p → q p \rightarrow q p→q。并称 p 为蕴含式的前件,q 为后件。并规定 p → q p \rightarrow q p→q 为假当且仅当 p 为真 q 为假。如果这个蕴含式成立(即公式本身是正确的),那我们就称 p蕴含q,记作 p ⇒ q p \Rightarrow q p⇒q
这个概念可能就有点抽象了,可以先看下离散数学中的关系闭包的概念。
这有点类似。
前面用 F 表示里面放了一堆函数依赖的集合。下面也同样这样表示。
定义:被 F 逻辑蕴涵的所有函数依赖集合称为 F 的 闭包(Closure),记作 F + F^+ F+ 。
如果 F + = F F^+ = F F+=F, 则说 F 是一个 全函数依赖族(函数依赖完备集)。
举个例子:
设 R = A ∨ B ∨ C , F = { A → B , B → C } R = A \vee B \vee C, F = \{A \rightarrow B, B \rightarrow C\} R=A∨B∨C,F={ A→B,B→C},
则 F + F^+ F+ 由如下形式的 X → Y X \rightarrow Y X→Y 构成:
这里我相当于是直接把模式R当成可见的标结构,而A,B,C当成是由属性组成的集合了。实际上模式应该不仅仅是可见的表结构,还包括一些依赖,比如外键约束,唯一约束等等。
在这里就是需要我们求出 F 中这两个函数依赖所蕴含的或者说能推导出的所有函数依赖。那要怎么求呢?这个似乎还蛮复杂的,就算要枚举也未必能穷尽,如果要找规律,似乎条件变了又不一样了,离散数学处理关系闭包的时候,也是没法直接用定义处理的,当时使用了好几个公式,如果希望自己寻求方案,可以去参考下,当然数据库的没这么麻烦,不过这里就不去讨论这个问题了。
看到闭包和完备集又让我想起了一些“旧事”
前面又看到了完备集!离散数学也有个联结词的完备集,还记得吗?
这里来回忆一下这个联结词完备集吧。(又沉迷了)
直白点讲,联结词语的完备集就是在说,有一个集合装的都是一些联结词,如果这些联结词能表示出所有的命题公式,这个集合就成了联结词的完备集了。
比如 { ¬ , ∧ , ∨ } \{ \neg, \wedge, \vee \} { ¬,∧,∨},就能使用表示所有的命题公式了,也就是说,使用一些运算律能把任意一个命题公式转换成这只用这几个联结词的公式。这里顺便提下一个可能被遗忘的东西——蕴含等值式: A → B ⇔ ¬ A ∨ B A \rightarrow B \Leftrightarrow \neg A \vee B A→B⇔¬A∨B,也是完成前面说的转化所需要用到的公式,这个就不证明了。
再来说下闭包,我们这个 F 放了一对的函数依赖,而这个函数依赖本质上就是一种关系。可以自己去了解一下关系的闭包。当然,无论是教材还是老师只会给出干巴巴的定义,也没有解释产生的背景和使用的价值。我个人是很讨厌学习这种横空出世的概念的。
这里先扔一个离散数学课本上的定义作为参考:
设R是集合A上的二元关系,R的自反(对称、传递)闭包是满足以下条件的关系R’:
- R’是自反的(对称的、传递的);
- R’⊇R;
- 对于A上的任何自反(对称、传递)关系R",若R"⊇R,则有R"⊇R’。
本质上讲关系的闭包是对某一不满足某种特性的关系进行最“经济”(即增加尽可能少的序对)的扩充,使之具有这一特性。这里的序对就是关系,特性就是定义中提到的自反,对称,传递这些。
xx特性对应的闭包就叫xx闭包,比如自反对应的就是自反闭包。
根据这个,我们的函数依赖对应的就叫函数依赖闭包喽。
我个人感觉,怎么样用最少的东西去完整描述的某一事物或某一特性,这就是闭包所解决的问题。(毕竟人类是个复杂的生物,但计算机却喜欢简单高效的表述)。当然,这是我只是个人对闭包理解,而且闭包不止这种抽象的闭包,
还有计算机编程中的闭包,比如 javascript 和 python 就使用名曰闭包的东西,来实现外部访问函数内部变量。此闭包是和彼闭包完全不相干呢,还是从另一个闭包的抽象中演化过来的呢?哎呀,太悬了,不想了!!!
想着想着,就想到其他地方去了,难怪学习老是学不进去!
在前面思考过程中,了解了函数依赖问题,也就是已知一组数据能否确定另一组数据的问题。
从完全和不完全函数依赖大概了解到, X → Y X \rightarrow Y X→Y 里,集合 X 中的一些属性可能是多余的。在完全函数依赖中 X 已经是最小的,所以 X 的真子集是没法确定 Y 的。
从传递函数依赖了解到,已知一组数据也许可以间接确定另一组数据,比如通过学号知道班级,再通过班号知道班长,而这有什么作用呢,我觉得如果将来表格要拆分并使用外键关联,或许我们可以参考这个传递函数依赖;安全问题上,通过分析传递函数依赖,避免外界推断出不该知道的内容。
接下来通过蕴含将函数依赖问题与数理逻辑中的推理进行了初次握手。在此基础上又碰到了了闭包的概念,从而意识到了函数依赖化简的问题。
下一步要学习的就是关于函数依赖的公理和定理以及数学运算了(前面的都是铺垫),比如Armstrong公理以及它的正确性证明,最小依赖,属性闭包的计算等,解决前面未解决的问题。玩的就是数学和心跳!
初步入坑函数依赖,如有错误,还请大家帮忙指正!