数据库范式设计
课程地址
范式设计的根本目的在于降低数据不一致造成风险的可能性
教科书级别的范式定义:符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度
从 1NF 到 5NF 是递进的关系,也就是满足了 2NF 一定满足了 1NF,满足了 3NF 一定满足了 1NF & 2NF
在实际应用中,以前 3 个范式最为重要
4NF 主要是在描述多对多关系如何建立,显然通过引入“关系表”来描述多对多关系比较合适
1NF
不能借助行的顺序传达任何信息
列的数据类型保持一致
必须要有主键
最重要的:符合1NF的关系中的每个属性都不可再分(原子性),例如如下多级表头的表格:
可以改进为如下的表格:
1NF是所有关系型数据库的最基本要求,也就是 MySQL 等 RDMS 天然地满足了 1NF
2NF
假设现在我们有一个游戏业务,需要记录每个玩家的 id,每个玩家持有的物品以及数量,还有每个玩家的等级
则我们设计出下面这张表:
这样做会造成删除异常、更新异常、新增异常:
假设 gila19 用户丢失了她的全部铜币(或者换成了其他物品),那么应当删除本行,但是这样做 PlayerRating 字段也被删除了,等级信息丢失,这是一种删除异常
假设我们更新 jdog21 用户的等级,正常情况下应该将 2 个 Intermediate 都更新为 Advanced,但是因为某些原因更新错误,这样就出现了逻辑的不一致,这是一种更新异常
假设我们想新增新用户 tina42,它的初始等级应该是 Beginner,但是因为她初始没有任何资产,所以 2 个 Item 列为空,不能插入数据,这是一种插入异常
第二范式:每个非主键列必须依赖于整个主键列的全部( Each non-key attribute must depend on the entire primary key)
或者反过来讲:表中的主键或联合主键,能够决定除该主键外的所有非主键值。通俗地讲:一张表只能描述一件事
上表中 ItemQuantity 列是符合第二范式的,因为物品数量既要表明它属于哪个用户,也要表明它是什么类型的物品,形式化表示为: { P l a y e r I D , I t e m T y p e } − > { I t e m Q u a n t i e y } \{PlayerID, ItemType\}->\{ItemQuantiey\} {PlayerID,ItemType}−>{ItemQuantiey},前者构成了主键的全部
但是 PlayerRating 明显只依赖于 PlayerID 列,而跟 ItemType 无关,形式化表示为: { P l a y e r I D } − > { P l a y e r R a t i n g } \{PlayerID\}->\{PlayerRating\} {PlayerID}−>{PlayerRating}
所以上表是一种不符合第二范式的依赖关系
我们意识到,PlayerRating 是 PlayerID 的一个重要属性(包括将来可能扩展的其他用户信息,包括用户年龄,性别等),这些信息应该单独拉到一个表中
而用户的资产独立到另外一个表中,使得 2 张表都能满足第二范式
3NF
我们继续扩充 Player 表,增加 PlayerSkillLevel 列,它的值从 1 到 9,与 PlayerRating 3 : 1 对应
假设某天将 gila19 的 PlayerSkillLevel 更新到 4,则需要将她的 PlayerRating 也更新到 Intermediate,但是这个过程一旦出错,就出现了数据不一致的问题
我们分析上面的依赖关系:
{ P l a y e r I D } − > { P l a y e r S k i l l L e v e l } \{PlayerID\}->\{PlayerSkillLevel\} {PlayerID}−>{PlayerSkillLevel}
{ P l a y e r I D } − > { P l a y e r S k i l l L e v e l } − > { P l a y e r R a t i n g } \{PlayerID\}->\{PlayerSkillLevel\}->\{PlayerRating\} {PlayerID}−>{PlayerSkillLevel}−>{PlayerRating}
上面 PlayerRating 依赖于 PlayerSkillLevel,这不符合第三范式:
第三范式:一个非主键类型的属性不能依赖于另一个非主键类型的属性(这种依赖可能会在更新时导致数据不一致)
为了解决这个问题,只需将 PlayerRating 依赖于 PlayerSkillLevel 提取到另外一张表即可
3NF: Every non-key attribute in a table should depend on the key, the whole key, and nothing but the key
BCNF: Every attribute in a table shold depend on the key, the whole key, and nothing but the key
4NF
设想我们需要存储多对多关系的场景,例如下面的 Model、Color 和 Style 之间构成了多对多的关系
当我们需要为 Model = Praire 新增一个颜色 Green 时,需要增加 2 个行,但是这样做一旦少增加了一行(Green Schoolhouse),就会造成数据不一致:只有绿色的 Bungalow 而没有绿色的 Schoolhouse
第四范式:表格中允许的多值依赖关系只能是对主键的多值依赖关系
5NF
设想这样一个场景,有 3 个冰激凌品牌:Frosty’s, Alpline, Ice Queen,每个品牌下面有若干口味
Jason 和 Suzy 分别有自己喜欢的品牌和口味,我们将其记录到表中
现在 Suzy 新增了一个喜欢的品牌:Frosty’s,因为她喜欢的口味是 rum raisin, mint chocolate chip and strawberry,后 2 者在 Frosty’s 中都有,所以需要新增 2 行
还是老问题,如果第二行新增失败,就会出现数据不一致的问题
解决办法还是解构(拆表格),分别记录每个人喜欢的品牌和口味
第五范式:数据库表格不能看作其他表格逻辑连接在一起的结果