SQL进阶理论篇(二):数据库的设计范式

文章目录

  • 简介
  • 数据库的设计范式有哪些
  • 数据库中的几种键
  • 从1NF到3NF
    • 1NF
    • 2NF
    • 3NF
    • BCNF(巴斯范式)
  • 反范式设计
  • 反范式的适用场景
  • 总结
  • 参考文献

简介

本小节主要内容:

  • 数据库的设计范式都有哪些
  • 数据库的键都有哪些
  • 1NF、2NF和3NF都是指什么?
  • 什么是BCNF
  • 什么是反范式设计,它有什么适用场景?

数据库的设计范式有哪些

范式(NF),可以理解成是一张数据表在设计时需要满足的某种设计标准的级别。

目前,关系型数据库一共有六种范式,按照范式级别,从低到高依次是:

  • 1NF,即第一范式
  • 2NF,即第二范式
  • 3NF,即第三范式
  • BCNF,即巴斯-科德范式
  • 4NF,第四范式
  • 5NF,第五范式,也叫做完美范式。

数据库的范式设计越高阶,冗余度就越低 ,同时,高阶范式一定满足低阶范式的要求,比如,满足2NF的一定满足1NF。

一般来讲,在关系型数据库里,数据表的设计应该尽量满足3NF,但是并不绝对,很多时候,出于提升查询性能的需要,我们会反范式设计,主动冗余一些字段。

在这里插入图片描述

数据库中的几种键

超键:只要是能唯一标识元组的属性集,都叫做超键。

候选键:是不含多余属性的超键,可以理解成是最小超键,从它的集合里找不到一个子集也是超键;

主键:从候选键中选择一个作为主键;

外键:不介绍了

主属性:包含在任一候选键里的属性,称为主属性;

非主属性:与主属性相对,不包含在任何一个候选键里的属性。

举个例子,以球员表为例,一个球员表包含球员的编号、姓名、证件号、年龄和球队编号。

而超键,就是包含球员编号或者证件号的任意组合,比如说(球员编号)、(证件号)、(证件号、姓名)、(证件号、姓名、年龄)等。

而候选键就是最小超键,即(球员编号)或者(证件号)。我们可以从它俩里选一个做主键。

外键就是球队编号。

主属性就是球员编号、证件号,剩下的都是非主属性。

从1NF到3NF

1NF

1NF是指数据库表中的任何属性都是原子性的,不可再分。事实上,任何DBMS都会满足第一范式的要求,不会将字段进行拆分。

2NF

2NF,指数据库表中的非主属性,都要和这个数据表的候选键有完全依赖关系。这里的完全依赖是指,依赖候选键的全部属性。

简单的说呢,就是针对概念上的联合主键,非主属性依赖于联合主键的每一个字段,而非部分字段

接下来,我们介绍一个没有满足2NF的例子。

我们设计了一张球员比赛表,其包含球员编号、姓名、年龄、比赛编号、比赛时间和比赛场地这些属性(字段)。

按照上一小节介绍的,这张表的候选键和主键都是(球员编号,比赛编号),因此我们可以得到以下关系:

(球员编号,比赛编号)决定了(姓名、年龄、比赛时间、比赛场地)

这个是不满足2NF的。

为啥这么说呢?

因为非主属性(姓名、年龄、比赛时间、比赛场地)并没有完全依赖主属性。

这张表里还存在以下关系:

(球员编号) 决定了 (姓名,年龄)
(比赛编号) 决定了 (比赛时间,比赛场地)

候选键的某个字段就可以决定非主属性。

这会产生什么问题呢?

  • 数据冗余:如果一个球员参加了m场比赛,那么球员的年龄和姓名数据就重复了m-1次。如果一场比赛有n个球员参加,那么比赛的时间和场地信息就重复了n-1次。
  • 插入异常:如果我们想添加一场新的比赛,但是还没有决定哪些球员能参加,这个数据是没法插入的;
  • 删除异常:如果我想删除某场比赛的信息,但是有球员只参加了这一场比赛的话,就会把这个球员的信息也给删掉;
  • 更新异常:如果一场比赛调整了比赛时间,那么我需要把这个表里这场比赛的全部记录都update时间。

为了避免以上情况,我们可以按照2NF,将球员比赛表设计为三张表:

  • 球员表,包含球员编号,姓名,年龄;
  • 比赛表,包含比赛编号、比赛时间、比赛场地;
  • 球员比赛表,包含比赛编号、球员编号和个人得分等各种字段。

所以,从某种程度上来讲,2NF是1NF的升级。1NF只告诉我们字段属性是独立的,而2NF告诉我们一张表就是一个独立的对象,即==一张表只表达一个意思==。

3NF

3NF在满足2NF的同时,要求任何非主属性都不传递依赖于候选键。也就是说,不能存在非主属性A依赖于非主属性B,而B依赖于候选键的情况。

在3NF里,每个非主属性,必须且只能直接依赖于全部候选键。可以理解成,非主属性只能直接依赖于全部主键字段。

再举一个例子,假设我们有这么一张球员表,其包含了球员编号、球员姓名、球队名称和球队教练。

这些字段的依赖关系如下图:

在这里插入图片描述

可以看到,球队教练这个属性,是传递依赖于球员编号的,这个就不符合3NF的要求。

如果要按照3NF来设计的话,上面应该分成两个表:

  • 球员表:球员编号、姓名和球队编号
  • 球队表:球队编号、球队名称和球队教练;

BCNF(巴斯范式)

举一个例子,假设我们有一张仓库管理表,包含以下字段:

在这里插入图片描述

在这个表里,我们指定一个仓库只能有一个管理员,且一个管理员只能管理一个仓库,即仓库和管理员严格一对一。

这样的话,仓库名决定了管理员,而管理员也决定了仓库名,同时,(仓库名,物品名)或者是(管理员,物品名)又决定了数量这个属性。

所以,这张表的候选键就是(仓库名,物品名)、(管理员,物品名)。我们随便挑一个候选键作为主键,比如(仓库名,物品名)。

于是,主属性就是仓库名、物品名、管理员,非主属性就是数量。

接下来,我们判断一下这张表的所属范式。

数据表每个属性都是原子性的,符合1NF的要求;

非主属性"数量"与候选键全部依赖,不存在仅使用候选键的某个子集就可以决定数量的情况。比如说仓库名或者物品名都无法决定数量。所以符合2NF的要求;

非主属性"数量"不传递依赖于候选键,而且也没有其他非主属性让它去传递依赖。所以符合3NF的要求。

综上,数据表符合3NF的要求,那是不是在设计上就没什么问题了呢?

不是,还是有很多问题:

  • 增加一个新仓库,但是没有存放任何物品。由于物品名是联合主键,不能为空,因此插入异常;
  • 仓库要更换管理员,需要修改这个仓库下所有物品的记录;
  • 仓库的商品卖空了,没有物品了。由于主键不能为空,这个仓库的信息只能删除,那么这个仓库的信息和对应的管理员信息也会被删除掉。

可以看到,即使一张表满足了3NF的要求,同样还是存在插入、更新和删除的问题。

这是为什么呢?

归根结底的原因是,主属性"仓库名"对候选键(管理员,物品名)是部分依赖的关系。“管理员"可以单独决定"仓库名”。

于是,人们在3NF的基础上进行了改进,提出了BCNF。

BCNF在3NF的基础上,进一步要求,主属性也不能对候选键有部分依赖或者传递依赖。

因此,根据BCNF,我们可以将上面的仓库管理表进一步细分成两张表:

  • 仓库表:仓库名,管理员;
  • 库存表:仓库名,物品名,数量;

反范式设计

越高阶的范式,设计出来的表数量就越多,数据冗余度就越低。但是这其实并不完全是好事,因为分化出来的表越多,意味着我们要做一个业务查询的时候,需要关联的表就越多。

因此,我们在实际生产中,经常为了性能和读取效率而做反范式的设计。即允许少量冗余字段的存在,以空间来换时间。

反范式,也是一种对查询效率的优化思路。

接下来,举一下教程里的例子,通过实验来模拟一下反范式的优化效果。

我们需要两张表

商品评论表,product_comment,对应字段如下:

在这里插入图片描述

用户表,user,对应的字段如下:

在这里插入图片描述

然后我们分别给两张表模拟出百万量级的数据,可以通过存储过程来实现。

给用户表随机生成100w用户:

CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_many_user`(IN start INT(10), IN max_num INT(10))
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE date_start DATETIME DEFAULT ('2017-01-01 00:00:00');
DECLARE date_temp DATETIME;
SET date_temp = date_start;
SET autocommit=0;
REPEAT
SET i=i+1;
SET date_temp = date_add(date_temp, interval RAND()*60 second);
INSERT INTO user(user_id, user_name, create_time)
VALUES((start+i), CONCAT('user_',i), date_temp);
UNTIL i = max_num
END REPEAT;
COMMIT;
END

在循环前,我们将 autocommit 设置为 0,这样等计算完成再统一插入,执行效率更高。

然后调用call insert_many_user(10000, 1000000);,生成编号从10000开始的百万用户数据,教程里是消耗了1分37秒。

接着给商品评论表随机生成100w条评论,内容为20个随机字母,存储过程如下:

CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_many_product_comments`(IN START INT(10), IN max_num INT(10))
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE date_start DATETIME DEFAULT ('2018-01-01 00:00:00');
DECLARE date_temp DATETIME;
DECLARE comment_text VARCHAR(25);
DECLARE user_id INT;
SET date_temp = date_start;
SET autocommit=0;
REPEAT
SET i=i+1;
SET date_temp = date_add(date_temp, INTERVAL RAND()*60 SECOND);
SET comment_text = substr(MD5(RAND()),1, 20);
SET user_id = FLOOR(RAND()*1000000);
INSERT INTO product_comment(comment_id, product_id, comment_text, comment_time, user_id)
VALUES((START+i), 10001, comment_text, date_temp, user_id);
UNTIL i = max_num
END REPEAT;
COMMIT;
END

然后调用call insert_many_product_comments(10000, 1000000),教程里是花了2分7秒。

如果我们现在接到一个需求,需要查询产品10001的前1000条评论,并且需要显示出对应的评论人姓名,需要写成:

SELECT p.comment_text, p.comment_time, u.user_name FROM product_comment AS p 
LEFT JOIN user AS u 
ON p.user_id = u.user_id 
WHERE p.product_id = 10001 
ORDER BY p.comment_id DESC LIMIT 1000

运行时长为0.395s。

效率不高。这是因为两个表都是百万量级的大表,关联的时候要做嵌套查询,消耗自然就上去了。

如果我们想提升查询的效率,就可以允许适当的字段冗余,比如说在商品评论表里增加用户名这个字段,这样一来,只需要单表查询就可以完成预定的需求,如:

SELECT comment_text, comment_time, user_name FROM product_comment2 WHERE product_id = 10001 ORDER BY comment_id DESC LIMIT 1000

消耗时间仅为0.039s。

反范式的适用场景

综上,反范式可以通过空间来换时间,从而提升查询效率。

但是在数据量小的情况下,反范式并体现不出优势,反而会把数据库的搞得更加复杂,比如说增加了更新的难度,可能要单独编写触发器之类的,自动更新。

反范式经常被用在数据仓库的设计之中。因为数仓通常是存储历史数据,以OLAP为主,对增删改的实时要求不高,反而是对历史数据的分析需求强。这时候适当增加数据的冗余度,更方便进行数据分析。

教程里,对下数据仓库和数据库在使用上的区别,是这么总结的:

  • 数据库设计的目的在于捕获数据,而数据仓库设计的目的在于分析数据;

  • 数据库对数据的增删改实时性要求强,需要存储在线的用户数据,而数据仓库存储的一般是历史数据;

  • 数据库设计需要尽量避免冗余,但为了提高查询效率也允许一定的冗余度,而数据仓库在设计上更偏向采用反范式设计。

其实上面的总结里还是保守了,数仓对反范式的应用是很广泛的。比如说数仓中,为了方便分析,经常会加工一些"宽表",冗余大量字段,来实现单表支撑分析的功能。另外,在数仓的分层里,从ODS到ADS,上层其实都是对下层的冗余。

总结

可以看到,从1NF到BCNF,突出的都是一个去冗余化,主打的就是一个精简,就如同"相同的代码不能出现两次",在范式设计的思想下,会重复出现的字段都应该抽离出来单独成表。

或者可以换个角度去思考,以BCNF一节中的例子来说,这个表里其实是有3个实体的,分别是仓库、管理员和物品。其中仓库:管理员是1:1,而仓库:物品是N:1,管理员:物品同样是N:1。

三个实体,且三个实体间是1:1:N。这就不符合范式设计的要求了,在范式设计下,单表里最好只保留两个及以下的实体,不同实体之间的关系最好是1:1或者1:N。因此优化后的例子里,把1:1:N拆分成了1:1和1:N两张表。

当然,实际设计数据表的时候,未必要符合这些原则,尤其是是在分布式的OLAP应用场景下。

一方面是这些范式本身就存在一些问题,在插入、更新和删除的时候可能会带来一些异常。另外,它们也会降低查询的效率,这是因为范式等级越高,设计出来的数据表就越多,实际做查询的时候就需要关联很多很多张表,从而降低查询效率。

说句题外话,我在教程的下面评论里看到一条极其有才的评论:

范式与反范式,正如传统与解构,规则与务实,稳定与突破,守成与创新,是阴阳动静的矛盾关系,两者一而二,二而一,即和而不同、求同存异,落脚点是务实,也就是应用场景和业务需求。
所以说,这已经不单是数据库设计的问题,而中国哲学体系在互联网商业中实践指导。
数据库设计提出范式的同时存在反范式的要求,符合否定之否定的螺旋上升轨迹,是数据库也是SQL语言保持强壮生命力而经久不衰的重要原因,是现实生存逻辑的映射。

看完当场下跪,这就是互联网的发言吗。

参考文献

  1. 21丨范式设计:数据表的范式有哪些,3NF指的是什么?
  2. 22丨反范式设计:3NF有什么不足,为什么有时候需要反范式设计?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/220107.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32的DMA的五大问题

1,DMA控制器的内部结构 STM32中的DMA控制器是一种用于在外设和存储器之间传输数据的专用硬件。DMA控制器的内部结构主要包括以下几个关键部分: 通道: DMA控制器可以有多个通道,每个通道独立管理一个数据传输任务。通道的数量取决于…

一进一出两线制(三线制)模拟量高速信号(50KHz)隔离变送器

一进一出两线制(三线制)模拟量高速信号(50KHz)隔离变送器 型号:JSD TA-2311F系列 产品特点: ◆小体积,低成本,标准 DIN35mm 导轨安装方式 ◆三端隔离(输入、输出、工作电源间相互隔离) ◆高速信号采集,隔离等(-3dB,Min≤ 3.5 uS) ◆高精度等级(0.1% F.S&…

mysql的redolog、undo、binlog的作用

概览: MySQL三大日志包括:undolog,redo log,binlog,它们分别有以下作用: undolog:是Innodb存储引擎事务生成的日志。用于事务的回滚和MVCC,保证了事务的原子性。 redo log&#x…

第二百零四回 模拟对话窗口的页面

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 经验分享5. 内容总结 我们在上一章回中介绍了"修改组件风格的另外一种方法"相关的内容,本章回中将介绍" 如何做一个模拟对话框窗口的页面".闲话休提,让我…

群晖(Synology)更换硬盘时间和精神双重折磨的教训

话说玩磁盘阵列的最后结果就是时间上负担不起,并且还被嫌弃。 在磁盘都到位后下一步就是要选择冗余类型了,对大部分人来说使用群晖自己提供的就好了,通常是 SHR。 什么是 SHR Synology Hybrid RAID(SHR)是 Synology…

【C语言程序设计】数组程序设计

目录 前言 一、数组的定义和初始化 二、数组的基本操作 三、数组的高级应用 四、程序设计 4.1 程序设计第一题 4.2 程序设计第二题 4.3 程序设计第三题 总结 🌈嗨!我是Filotimo__🌈。很高兴与大家相识,希望我的博客能对你有所帮助…

【什么是POI,为什么它会导致内存溢出?】

什么是POI,为什么它会导致内存溢出 什么是POIExcel并没看到的那么小POI的溢出原理 拓展知识几种Workbook格式 什么是POI Apache POl,是一个非常流行的文档处理工具,通常大家会选择用它来处理Excel文件。但是在实际使用的时候经常会遇到内存溢…

textarea 网页文本框在光标处添加内容

在前端研发中我们经常需要使用脚本在文本框中插入内容。如果产品要求不能直接插入开始或者尾部,而是要插入到光标位置,此时我们就需要获取光标/光标选中的位置。 很多时候,我在格式化文本处需要选择选项,将选择的信息输入到光标位…

html 中vue3 的setup里调用element plus的弹窗 提示

引入Elementplus之后&#xff0c;在setup&#xff08;&#xff09;方法外面导入ElMessageBox const {ElMessageBox} ElementPlus 源码 &#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><!-- import Vue before Elemen…

CAN 三: STM32 CAN相关寄存器介绍

1、寄存器列表&#xff08;F1/F4/F7&#xff09; 寄存器名称作用CAN_MCRCAN主控制寄存器主要负责CAN工作模式的配置CAN_BTR位时序寄存器用来设置分频/TBS1/TBS2/TSWJ等参数&#xff0c;设置测试模式CAN_(T/R)IxR标识符寄存器存放(待发送/接收)的报文ID、扩展ID、IDE位及RTR位C…

SpringBoot之实体参数的详细解析

1.3 实体参数 在使用简单参数做为数据传递方式时&#xff0c;前端传递了多少个请求参数&#xff0c;后端controller方法中的形参就要书写多少个。如果请求参数比较多&#xff0c;通过上述的方式一个参数一个参数的接收&#xff0c;会比较繁琐。 此时&#xff0c;我们可以考虑…

XSS防御:内容安全策略 CSP工作原理、配置技巧与最佳实践

前言 公司部门安全合规改造计划&#xff0c;要求所有的Web站点统一添加CSP规则。对于CSP机制我只是之前在应付面试的时候背过相关的概念&#xff0c;并没有真正在项目中实践过。所以希望借助本次改造任务好好理解并实践CSP机制。 什么是CSP CSP的全称是 Content Security Po…

CCF 202104-2:邻域均值--C++

#include<iostream> #include<bits/stdc.h>using namespace std;int A[601][601]; int n;//长宽都为n个像素double FindNeighborSum(int i,int j,int r,int A[][601]) {int sum0;//像素和 int gs0;//领域 中的像素个数 for(int xi-r;x<ir;x)//找到每一个领域像素…

数据分析基础之《numpy(3)—基本操作》

一、基本操作 1、adarray.方法() 2、np.函数名() 二、生成数组的方法 1、生成0和1的数组 为什么需要生成0和1的数组&#xff1f; 我们需要占用位置&#xff0c;或者生成一个空的数组 &#xff08;1&#xff09;ones(shape[, dtype, order]) 生成一组1 shape&#xff1a;形…

NOIP2017提高组day2 - T2:宝藏

题目链接 [NOIP2017 提高组] 宝藏 题目描述 参与考古挖掘的小明得到了一份藏宝图&#xff0c;藏宝图上标出了 n n n 个深埋在地下的宝藏屋&#xff0c; 也给出了这 n n n 个宝藏屋之间可供开发的 m m m 条道路和它们的长度。 小明决心亲自前往挖掘所有宝藏屋中的宝藏。但…

【沐风老师】科研绘图3DMAX病毒建模教程

3dMax在科研绘图方面也有广泛的应用&#xff0c;本教程就给大家讲解病毒的建模方法&#xff0c;下面直接进入教程&#xff1a; 3dMax病毒建模方法&#xff1a; 1.启动3dMax&#xff0c;在视口中创建一个“几何球体”。方法&#xff1a;右边命令面板->创建->几何体->…

MySQL中EXPLAIN执行计划的分析

一. 执行计划能告诉我们什么&#xff1f; SQL如何使用索引联接查询的执行顺序查询扫描的数据函数 二. 执行计划中的内容 SQL执行计划的输出可能为多行&#xff0c;每一行代表对一个数据库对象的操作 1. ID列 ID列中的如果数据为一组数字&#xff0c;表示执行SELECT语句的顺…

当当狸AR智能学习图集跨越千年文明传承,邀您“面对面”与虚拟诗人互动对诗

中华传统文化底蕴深厚&#xff0c;余韵悠长。即使经过千年的历史裂变&#xff0c;依然历久铭心慰藉着一代又一代人的灵魂。千百年后的今天&#xff0c;成为了我们独一无二的财富。 如今&#xff0c;国人学习中华传统文化的方式有很多&#xff0c;诗词集、动画影片、诗歌传颂等…

Java,这是一个模仿HashMap的put,get功能的自定义MyHashMap

Java 手写HashMap源码 一&#xff0c;手写源码 这是一个模仿HashMap的put&#xff0c;get功能的自定义的MyHashMap package cn.wxs.demo;import java.io.Serializable; import java.util.*; import java.util.function.BiConsumer; import java.util.function.BiFunction; i…

SIM初始化流程

ATR ATR(Answer To Reset)&#xff1a;复位应答信号&#xff0c;有SIM卡传输给终端&#xff0c;包括SIM卡自身的一些信息&#xff0c;比如支持的传输速率&#xff0c;传输模式等。 SIM卡的ATR代表"Answer to Reset"&#xff0c;即复位响应。当SIM卡被插入设备中时…