一、概述
前段时间自己写项目,在设计数据库的过程中,重新审视了关系型数据库,在此记录整理如下。
什么是关系型数据库的范式?
数据库规范化 - Normal Form,又称正规化、标准化,是数据库设计中的一系列原理和技术,以减少数据库中数据冗余,增进数据的一致性。关系模型的发明者埃德加·科德最早提出这一概念,并于1970年代初定义了第一范式、第二范式和第三范式的概念,还与Raymond F. Boyce于1974年共同定义了第三范式的改进范式——BC范式。
数据库规范化有什么用?
增进数据一致性,减少数据冗余。
二、范式定义
范式现在发展出了很多种。一般来说,现在数据库设计最多满足1NF - 3NF,范式过高虽然对数据关系有更好的约束性,但也导致数据关系表增加而令数据库IO更易繁忙。
1NF-3NF,每一级NF都必须满足上一级NF:
1NF:列必须是原子的(列的原子性),不可再分;
2NF:非主键列必须完全依赖于主键,而不是只依赖某些主键列;
3NF:非主键列必须直接依赖于主键列,不能存在传递依赖(传递依赖:非主属性A依赖非主属性B,B再依赖主属性C);
三、范式详解
1. 1NF
1.1 解决的问题
排除重复组
1.2 例子
Table_交易↓ 如下
顾客 | 日期 | 数量 |
---|---|---|
Pete | Monday | 19.00 -28.20 |
Pete | Monday | -84.00 |
Sarah | Friday | 100.00 -48.20 |
数量就是“重复组”了。
1.3 解析
理论上不可能有RDBMS能让你设计出违反1NF的数据表,也就是说RDBMS已经保证其列的原子性了。例如,你定义了一个整数字段来存放数量,你肯定写不出”25,30”这样的值,它是违法的(如果你硬把2530连着写,那表示这是一个原子值)。
不过就算这样,你还是可以设计出骨子里违反1NF的表,例如
单一字段中有多个有意义的值
Table_不喜欢的食物↓
人 不喜欢的食物(varchar类型) Pete 白菜,面包 Pete 泡菜,白菜,西兰花 a) 显然“不喜欢的食物”是一个RDBMS保证的原子列,但是你利用他是varchar这一点,来自己意淫设计存储了多个逻辑值,然后用逗号分隔他们…… 相信很多人这样干过
b) 以这样的设计,如果要查询不喜欢白菜的人就很不清晰该怎么做了(还能叫关系型数据库表吗)可能必须使用like%%了。
用很多字段来表达一个事实
Table_不喜欢的食物↓
人 不喜欢的食物1 不喜欢的食物2 不喜欢的食物3 Pete 白菜 小龙虾 芹菜 Alan 泡菜 龙虾 大闸蟹 a) 就算我们能确定每个人不喜欢吃的食物最多不会超过三样,这还是一个很糟的设计。举例来说,我们想要知道所有不喜欢同一种食物的人的组合的话,这就不是件容易的事,因为食物有可能出现在任何一个字段,也就是说每一次的查询都要去检查 9 (3 x 3) 组不同的字段组合。
2. 2NF
1.1 解决的问题
1 如果一个数据表只有单一一个主键字段的话,它就一定符合第二范式。
2 错误一般只会发生在想设计有一对多/多对一中多的一方或者是多对多关联表中的联合主键时发生。
1.2 例子
Table_组件来源↓
组件ID(主键) | 价格 | 供应商ID(主键) | 供应商名称 | 供应商住址 |
---|---|---|---|---|
65 | 59.99 | 1 | Stylized Parts | VA |
74 | 20.00 | 1 | Stylized Parts | VA |
65 | 69.99 | 2 | ACME Industries | CA |
1.3 解析
首先,上表是按多对一设计的(设计错误问题正是我们要说的),1个组件来自于1个供应商,1个供应商可能供应多个组件。
其次,上表满足了列的原子性,所以符合第一范式。组件ID和供应商ID是联合主键。
价格根据不同组件和不同供应商才能确定,没问题。
但供应商名称和供应商地址只是依赖于供应商ID的,跟组件没有关系,这就叫非完全依赖。
正确设计
Table_供应商↓
供应商ID(主键) 名称 价格 1 Stylized Parts VA 2 ACME Industries CA Table_组件来源↓
组件ID(主键) 价格 供应商ID(主键) 65 59.99 1 74 20.00 1 65 69.99 2
3. 3NF
1.1 解决的问题
在2NF基础上,非主键列必须直接依赖于主键。(非主键列之间是独立的没有关系)
1.2 解析
例子其实可以是上边2NF的列子,它也是不符合3NF的。
在做数据库表设计时,仔细考虑你写下的非关键字段到底是否直接依赖于主键,这样就能遵守第三范式了。