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

news2024/9/29 5:26:02

文章目录

  • 简介
  • 数据库的设计范式有哪些
  • 数据库中的几种键
  • 从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.coloradmin.cn/o/1309560.html

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

相关文章

初识大数据应用,一文掌握大数据知识文集(1)

文章目录 🏆初识大数据应用知识🔎一、初识大数据应用知识(1)🍁 01、请用Java实现非递归二分查询?🍁 02、是客户端还是Namenode决定输入的分片?🍁 03、mapred.job.tracker命令的作用?…

00TD I 秒变甜美小淑女,冬日氛围感拉满

一衣两穿的的气质款羽绒服 穿着轻松营造氛围感 90白鸭绒填充 厚实蓬松保暖性十足,大大的翻领显得脸小 又增添精致感,腰间系带系起来就像 穿了件可爱精致的小裙子般 解开也是另一种简约气质的感觉 小淑女们衣橱必入款。

水闸水雨情监测设施建设项目

功能设计 在水闸上、下游挡墙外侧各安装1套雷达水位计,水闸屋顶布置个雨量计,水位及雨量监测数据的采集与传输主要是实时的完成水位、雨量数据的采集与处理,并按照设定的工作方式、时间间隔、增量范围将数据上传至扬压力监测站边缘计算终端&…

【数据结构和算法】压缩字符串

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一:双指针 三、代码 3.1 方法一:双指针 四、复杂度分析 前言 这是力扣…

如何搭建适合自己的数字人源码系统?

随着数字人技术的快速发展,数字人直播软件成为了直播行业的热门话题。数字人源码部署成为了创业者在这个领域看中的新商机,数字人直播软件给市场带来很多机会的同时也给商家带来了更多的收益。但是,选择一个适合自己的数字人直播软件源码并不…

模型数据-HttpServletRequest使用

模型数据-HttpServletRequest使用 三种情况 自己也要添加一些属性到Request域中,可以通过原生Servlet API的request来设置.也可以修改按照默认机制已经放进去 的某些对象.明白默认情况下存放的名字其实就是类名/类型名 首字母小写.(虽然monster100改了,但还是按类名首字母小写…

大模型时代-从0开始搭建大模型

开发一个简单模型的步骤; 搭建一个大模型的过程可以分为以下几个步骤: 数据收集和处理模型设计模型训练模型评估模型优化 下面是一个简单的例子,展示如何使用Python和TensorFlow搭建一个简单的大模型。 数据收集和处理 首先,我…

node-static 任意文件读取漏洞复现(CVE-2023-26111)

0x01 产品简介 node-static 是 Node.js 兼容 RFC 2616的 HTTP 静态文件服务器处理模块,提供内置的缓存支持。 0x02 漏洞概述 node-static 存在任意文件读取漏洞,攻击者可通过该漏洞读取系统重要文件(如数据库配置文件、系统配置文件&#…

Java使用Microsoft Entra微软 SSO 认证接入

1. Microsoft Entra Microsoft Entra ID 是基于云的标识和访问管理服务,可帮助员工访问外部资源。 示例资源包括 Microsoft 365、Azure 门户以及成千上万的其他 SaaS 应用程序。 Microsoft Entra ID 还可帮助他们访问你的企业 Intranet 上的应用等内部资源&#x…

行业报告 | 中国工业机器人的“春天”

原创 | 文 BFT机器人 前言: 工业机器人是先进制造业中不可替代的重要装备,是衡量国家制造业水平和科技水平的重要标志。中国作为世界公认的制造业第一大国,随着人口红利的逐渐消退,劳动力成本不断提高,生产自动化和发…

纺织辅料企业网站搭建的效果如何

纺织品是日常生活必需品,服装、鞋帽箱包等品牌企业很多,纺织辅料的需求度也很高,对企业商家来说,市场高需求度的同时也面临着一些痛点: 1、品牌宣传、产品展示难 纺织辅料产品属于国内外都可以拓展,并没有…

Node.js创建一个简单的WebSocket接口,实现通信交互

Node.js创建一个简单的WebSocket接口,实现通信交互 一、为什么使用WebSocket? WebSocket,最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话&#xf…

Paper Reading: (U2PL) 基于不可靠伪标签的半监督语义分割

目录 简介目标/动机方法Pseudo-LabelingUsing Unreliable Pseudo-Labels 补充知识InfoNCE LossOHEM 实验Comparison with Existing AlternativesAblationEffectiveness of Using Unreliable Pseudo-LabelsAlternative of Contrastive Learning 总结附录U2PL 与 negative learni…

Unity之OpenXR+XR Interaction Toolkit接入Meta Quest3

前言 随着备受期待的Meta Quest 3与今年10月10日发布,这款来自Meta的下一代VR游戏头戴设备承诺将彻底改变您的游戏方式。 Meta Quest 3,玩家只需轻松一触即可在虚拟现实和真实世界之间无缝切换,无需摘下头戴设备进行快速现实检查。 Meta Quest 3最引人注目的特点之一是其能…

大数据讲课笔记1.2 Linux用户操作

文章目录 零、学习目标一、导入新课二、新课讲解(一)用户账号管理1、用户与用户组文件2、用户账号管理工作 (二)用户操作1、切换用户(1)语法格式(2)切换到普通用户(3&…

Axure元件库的使用

1.基本元件库 1.1Axure的画布范围 Axure是一个绘制项目原型图的软件,它里面的基本原件有: 1.1元件的呈现范围 首先我们要了解基本元件的作用范围在哪里? 浏览效果: 可以看出当我们的基本元件放在画布区域内是可以完全呈现出来…

智能安全用电监控管理系统

智能用电安全监控管理系统是一种基于物联网技术和大数据分析的智能化管理系统,用于监控和管理电力设备的安全运行。该系统依托电力智慧运维工具-电易云,可以实时监测电力设备的运行状态、电力负荷、用电行为等信息,通过智能算法识别设备的异常…

图像截屏公式识别——LaTeX-OCR安装与使用

一、简介 LaTeX-OCR 是一个开源的光学字符识别(OCR)软件,专为 LaTeX 文档提供支持。其主要目的是帮助用户将扫描的文档转换为 LaTeX 编辑器可以使用的可编辑文本,从而方便进行修改、编辑和排版。LaTeX广泛用于科技、数学、工程等…

leetcode--3. 无重复字符的最长子串[滑动窗口\哈希表 c++]

原题 : 3. 无重复字符的最长子串 - 力扣(LeetCode) 题目解析: 最长子串可以用滑动窗口解决,无重复字符可以使用哈希表解决。 算法原理: 滑动窗口哈希表 哈希表作为一个数组存放每个字符出现的次数。 …

串口通信(4)-C#串口通信入门实例

本文通过实例讲解C#串口通信。 入门实例设计一个串口助手,能够很好的涵盖串口要点的使用。 目录 一、成品图 二、界面文件 三、后台代码 四、实例中要点 一、成品图 如下: 实现的过程 创建winform项目,将Form1文件的名称改为MainForm&…