1

数据表的设计和工作流程需要规范性。

范式是数据表设计的基本原则,很容易被忽视。很多时候,当数据库运行了一段时间之后,我们才发现数据表设计得有问题。重新调整数据表的结构,可能需要做数据迁移,并且甚至可能会影响程序的业务逻辑。所以在开始设计数据库的时候,我们就需要重视数据表的设计。

数据库的范式有哪些?

我们在设计关系型数据库模型的时候,需要对关系内部各个属性之间联系的合理化程度进行定义,这就有了不同等级的规范要求,这些规范要求被称为范式(NF)——数据表的设计结构需要满足的某种设计标准的级别。

目前关系型数据库一共有6种范式,按照级别从低到高分别是:1NF(第一范式),2NF(第二范式),3NF(第三范式),BCNF(巴斯-科德范式),4NF(第四范式),5NF(第五范式).

数据库的范式设计越高阶,冗余度就越低,高阶范式一定满足低阶范式的要求。

数据表中的键

范式的定义会使用到主键和候选键(因为主键和候选键可以唯一标识元组),数据库种的键由一个或多个属性组成。

  • 超键:能唯一标识元组的属性集叫做超键
  • 候选键:如果超键不包括多余的属性,则这个超键就是候选键,就是说候选键一定是超键
  • 主键:用户可以从候选键种选择其中一个作为主键
  • 外键:如果数据表T1种的某属性集不是T1的主键,而是另外一张表T2的主键,那么这个属性集就是T1的外键
  • 主属性:包含在任一候选键种的属性称为主属性
  • 非主属性:跟主属性相对

从1NF到3NF

1NF是数据库表种的任何属性都是原子的,不可再分。

任何的DBMS都会满足1NF的要求,除非在设计的时候设计一个属性X,属性Y是X的前半段,属性Z是X的后半段,并且Y和Z没有什么使用意义,我想应该没有人当这样的折磨王吧。

2NF要求数据表里的非主属性都要和这个数据表的候选键有完全依赖关系。

这里我举一个没有满足2NF的例子。

假设现在有一张球员比赛表,里面包含球员编号,姓名,年龄,比赛编号,比赛时间,比赛场地这些属性。这里的候选键和主键都是(球员编号,比赛编号),可以通过这个候选键决定其他的属性:

(球员编号,比赛编号) → (姓名,年龄,比赛时间,比赛场地)。

但是这个表不满足2NF,因为还存在下面的关系:

(球员编号) → (姓名,年龄)

(比赛编号) → (比赛时间,比赛场地)

也就是说候选键的部分字段决定了非主属性,那这样会产生什么问题?

  1. 数据冗余:球员可以参加多场比赛,那么球员的姓名和年龄就重复了多次。同样,一场比赛有多个球员参与,比赛时间和比赛场地就重复了多次。
  2. 插入异常:联盟新增了新球员,但是还在休赛期,比赛相关的字段就插入异常。就是候选键部分字段以及他们决定的非主属性有数据,但是候选键其他部分的字段以及其他非主属性还未产生数据时,就插入异常。
  3. 删除异常:如果有一场比赛只有一个球员参与,这个球员这个赛季退役了,删除这个球员的时候同时也删除了这场比赛了。
  4. 更新异常:当一个球员的年龄变化了,需要更新这个球员参加的所有比赛的行。
  5. 总的来说,第1个问题导致了后面3个问题,因为数据冗余,导致数据的增删改查都不对劲。

为满足2NF改正:

将这张表分为3张表

player:球员编号,姓名,年龄,队伍

game:比赛编号,比赛时间,比赛场地

player_game:比赛编号,参赛球员编号,参赛球员得分

将刚才不满足2NF的关系拆分成不同的表,并且使用第3张表进行链接他们。这样每一张表都满足2NF。

3NF在满足2NF的同时,对任何非主属性都不传递依赖于候选键

举个例子,现在有个cdn_load的表,其中有自增主键id,时间戳event_timestamp,资源类型cdn_type,加载该资源的页面名称page_name,是不是游戏平台的域名is_game_platform,域名还是具体的链接url_type。

候选键有:(id)和(event_timestamp, cdn_type, page_name, url)

选择id作为主键时
image

有以上依赖关系

左边这个关系中,主键id可以决定event_timestamp,url,is_game_platform,但是这里有一个非主属性决定了另一个非主属性,url可以决定is_game_platform,所以is_game_platform通过url传递依赖主键id。

同样右边的关系中,url_type通过url传递依赖主键id。

其实is_game_platform和url_type可以不存入DB,删除is_game_platform和url_type,直接通过程序计算所得即可,这样就满足了3NF。

如果一定要这两个字段并且一定要满足3NF,那么拆分成两张表

cdn_request:除去is_game_platform和url_type的其他字段。

url_info:url,is_game_platform,url_type

这样两张表都满足3NF。

反范式设计

尽管围绕着数据表的设计有很多范式,但事实上我们在设计数据表的时候却不一定要参照这些标准。

我们知道越高阶的范式,数据冗余越低,但是有时候,我们在设计数据表的时候,还需要为了性能和读取效率违反范式原则。换句话说,就是允许少量的冗余,通过空间换时间。

比如博客评论这个场景

假设设计有以下两张表:

comment:comment_id/blog_id/comment_text/time/user_id

user_id:user_id/user_name/create_time

因为在评论表中,我们不会只展示用户id,会展示用户的名称,所以查询评论列表的时候,需要连表查询,增加耗时。

在这个场景中,我们为了让评论列表的加载耗时减少,可以在comment表中增加一列user_name,这样做虽然违反了3NF,但是增加了一点冗余的前提下换来了加载速度的提升,在业务中如果评估合理是可以采用这样的反范式设计的。

还有像上述cdn_load表的反范式设计,这里增加两个传递依赖主键的字段,可以直接从DB中获取而不需要计算获得,减少程序的计算度。(PS:个人认为这点计算量基本可以忽略不计)


一画先生
83 声望12 粉丝

我司长期招聘前端开发工程师,有意的小伙伴+vx: Mr_yihua