数据库表设计有很多种范式,这里主要写三种常见范式,以备面试使用。
第一范式
第一种范式处理记录类型的“形状”,也就是规定表的二维结构:
- 每一行都有相同的列;
- 没有重复的列和行;
本质上这一定义也是关系型数据库的基本定义。
对比
和MongoDB等schemaless的数据库比较可以发现,MongoDB这类数据库的记录(Mongo称之为Document)是没有这种二维结构的,对js一类的脚本语言更友好,一个Document可以直接映射成一个对象,同一个collection的Document也不一定有相同的字段,甚至同一个collection里可能两个Document没有一个相同的字段。
这让mongodb这类数据库有足够的灵活性应对复杂的业务场景,开发简单,运维难度比传统RDBMS小得多。
而像是Redis这样的KV数据库则比传统RDBMS更加简化,没有表的概念,直接把数据结构暴露给用户。这让redis简单快速,但不适合应付复杂的业务场景。因此redis大多时候就是拿来当缓存和共享数据的。
第二范式
第二范式和第三范式处理键(key)字段和非键字段之间的关系,在第一范式基础上加入新的原则:任意一个非主属性都完全函数依赖于主键。
举例来说,在一个系统内部的通讯录表中。
姓名 | 固定电话 | 地址 |
---|---|---|
张三 | xxx-xxx-xxxx | 派出所 |
张三 | yyy-yyy-yyyy | 看守所 |
李四 | yyy-yyy-yyyy | 看守所 |
假设一个地址只有一个固定电话可用,我们注意到电话是和地址相关的,但并不完全函数依赖于主键,也就是姓名。
这样会造成很多问题:
- 冗余。
- 更新地址或电话需要更新多条记录
等等。正确做法是拆成两个表,其中一个表以姓名和地址作为主键。
这段比较绕,我也头晕。建议读读参考链接里的文章。
第三范式
第三范式的主要是在第二范式基础上规定消除传递依赖性。
举个工资表的例子。
姓名 | 职务 | 职称 | 基本薪资 | 职务工资 |
---|---|---|---|---|
张三 | MT | 高级干员 | 10000 | 2000 |
李四 | TK | 中级干员 | 8000 | 1500 |
王五 | 划水 | 初级干员 | 4000 | 0 |
首先我们了解到,基本薪资是由职称决定的。可以注意到,其中每个字段都和主键姓名成完全函数依赖,通过姓名可以唯一确定这人的基本薪资和职务工资,符合第二范式;
但如果注意看的话会注意到,主键姓名先唯一确定了职务和职称,再由职务和职称间推出实际工资数字的。这种依赖称为基本薪资传递函数依赖于主键姓名。
如果要符合第三范式,那么应该拆分为三张表:
- 职务工资表
- 职称工资表
- 员工表
其中员工表应该是这样
姓名 | 职务 | 职称 |
---|---|---|
张三 | MT | 高级干员 |
李四 | TK | 中级干员 |
王五 | 划水 | 初级干员 |
职务工资表如下
职务 | 工资 |
---|---|
MT | 2000 |
TK | 1500 |
划水 | 0 |
基本工资同理,不列举了。