MySQL性能优化-范式设计和反范式设计

范式化设计

范式化设计背景

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

数据库的设计范式都包括哪些

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

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

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

一般来说数据表的设计应尽量满足 3NF。但也不绝对,有时候为了提高某些查询性能,我们还需要破坏范式规则,也就是反规范化。

数据表中的那些键

范式的定义会使用到主键和候选键(因为主键和候选键可以唯一标识元组),数据库中的键(Key)由一个或者多个属性组成。我总结了下数据表中常用的几种键和属性的定义:

超键:能唯一标识元组的属性集叫做超键。
候选键:如果超键不包括多余的属性,那么这个超键就是候选键。
主键:用户可以从候选键中选择一个作为主键。
外键:如果数据表 R1 中的某属性集不是 R1 的主键,而是另一个数据表 R2 的主键,那么这个属性集就是数据表 R1 的外键。
主属性:包含在任一候选键中的属性称为主属性。
非主属性:与主属性相对,指的是不包含在任何一个候选键中的属性。

通常,我们也将候选键称之为“码”,把主键也称为“主码”。因为键可能是由多个属性组成的,针对单个属性,我们还可以用主属性和非主属性来进行区分。

举例说明:

使用的表:
球员表定义为包含球员编号、姓名、身份证号、年龄和球队编号;
球队表包含球队编号、主教练和球队所在地。

对于球员表来说,超键就是包括球员编号或者身份证号的任意组合,比如(球员编号)(球员编号,姓名)(身份证号,年龄)等。

候选键就是最小的超键,对于球员表来说,候选键就是(球员编号)或者(身份证号)。

主键是我们自己选定,也就是从候选键中选择一个,比如(球员编号)。

外键就是球员表中的球队编号。

在 player 表中,主属性是(球员编号)(身份证号),其他的属性(姓名)(年龄)(球队编号)都是非主属性。

从第一范式到第三范式

1NF 指的是数据库表中的任何属性都是原子性的,不可再分。这很好理解,我们在设计某个字段的时候,对于字段 X 来说,就不能把字段 X 拆分成字段 X-1 和字段 X-2。事实上,任何的 DBMS 都会满足第一范式的要求,不会将字段进行拆分。

*2NF 指的数据表里的非主属性都要和这个数据表的候选键有完全依赖关系。*所谓完全依赖不同于部分依赖,也就是不能仅依赖候选键的一部分属性,而必须依赖全部属性。

举例说明:

这里我举一个没有满足 2NF 的例子,比如说我们设计一张球员比赛表 player_game,里面包含球员编号、姓名、年龄、比赛编号、比赛时间和比赛场地等属性,这里候选键和主键都为(球员编号,比赛编号),我们可以通过候选键来决定如下的关系:

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

上面这个关系说明球员编号和比赛编号的组合决定了球员的姓名、年龄、比赛时间、比赛地点和该比赛的得分数据。

但是这个数据表不满足第二范式,因为数据表中的字段之间还存在着如下的对应关系:

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

也就是说候选键中的某个字段决定了非主属性。你也可以理解为,对于非主属性来说,并非完全依赖候选键。这样会产生怎样的问题呢?


数据冗余:如果一个球员可以参加 m 场比赛,那么球员的姓名和年龄就重复了 m-1 次。一个比赛也可能会有 n 个球员参加,比赛的时间和地点就重复了 n-1 次。
插入异常:如果我们想要添加一场新的比赛,但是这时还没有确定参加的球员都有谁,那么就没法插入。
删除异常:如果我要删除某个球员编号,如果没有单独保存比赛表的话,就会同时把比赛信息删除掉。
更新异常:如果我们调整了某个比赛的时间,那么数据表中所有这个比赛的时间都需要进行调整,否则就会出现一场比赛时间不同的情况。

为了避免出现上述的情况,我们可以把球员比赛表设计为下面的三张表。

球员 player 表包含球员编号、姓名和年龄等属性;
比赛 game 表包含比赛编号、比赛时间和比赛场地等属性;
球员比赛关系 player_game 表包含球员编号、比赛编号和得分等属性。

这样的话,每张数据表都符合第二范式,也就避免了异常情况的发生。某种程度上 2NF 是对 1NF 原子性的升级。1NF 告诉我们字段属性需要是原子性的,而 2NF 告诉我们一张表就是一个独立的对象,也就是说一张表只表达一个意思。

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

我们用球员 player 表举例子,这张表包含的属性包括球员编号、姓名、球队名称和球队主教练。现在,我们把属性之间的依赖关系画出来,如下图所示:
在这里插入图片描述

你能看到球员编号决定了球队名称,同时球队名称决定了球队主教练,非主属性球队主教练就会传递依赖于球员编号,因此不符合 3NF 的要求。

如果要达到 3NF 的要求,需要把数据表拆成下面这样:

球员表的属性包括球员编号、姓名和球队名称;
球队表的属性包括球队名称、球队主教练。

我再总结一下,1NF 需要保证表中每个属性都保持原子性;2NF 需要保证表中的非主属性与候选键完全依赖;3NF 需要保证表中的非主属性与候选键不存在传递依赖。

范式化设计总结

我们今天讲解了数据表设计的三种范式。关系型数据库的设计都是基于关系模型的,在关系模型中存在着 4 种键,这些键的核心作用就是标识。

在这些概念的基础上,我又讲了 1NF,2NF 和 3NF。我们经常会与这三种范式打交道,利用它们建立冗余度小、结构合理的数据库。

有一点需要注意的是,这些范式只是提出了设计的标准,实际上设计数据表时,未必要符合这些原则。一方面是因为这些范式本身存在一些问题,可能会带来插入,更新,删除等的异常情况(这些会在下一讲举例说明),另一方面,它们也可能降低会查询的效率。这是为什么呢?因为范式等级越高,设计出来的数据表就越多,进行数据查询的时候就可能需要关联多张表,从而影响查询效率。
在这里插入图片描述

反范式化设计

BC范式(巴斯范式)

如果数据表的关系模式符合 3NF 的要求,就不存在问题了吗?我们来看下这张仓库管理关系 warehouse_keeper 表:
在这里插入图片描述

在这个数据表中,一个仓库只有一个管理员,同时一个管理员也只管理一个仓库。我们先来梳理下这些属性之间的依赖关系。

(仓库名) -> (仓库管理员)
(仓库管理员) -> (仓库名)
(仓库名,物品名) -> (物品数量)

这样,我们就可以找到数据表的候选键是(管理员,物品名)和(仓库名,物品名),

然后我们从候选键中选择一个作为主键,比如(仓库名,物品名)。

在这里,主属性是包含在任一候选键中的属性,也就是仓库名,管理员和物品名。非主属性是数量这个属性。

首先,数据表每个属性都是原子性的,符合 1NF 的要求;其次,数据表中非主属性”数量“都与候选键全部依赖,(仓库名,物品名)决定数量,(管理员,物品名)决定数量,因此,数据表符合 2NF 的要求;最后,数据表中的非主属性,不传递依赖于候选键。因此符合 3NF 的要求。

既然数据表已经符合了 3NF 的要求,是不是就不存在问题了呢?我们来看下下面的情况:

增加一个仓库,但是还没有存放任何物品。根据数据表实体完整性的要求,主键不能有空值,因此会出现插入异常;
如果仓库更换了管理员,我们就可能会修改数据表中的多条记录;
如果仓库里的商品都卖空了,那么此时仓库名称和相应的管理员名称也会随之被删除。

你能看到,即便数据表符合 3NF 的要求,同样可能存在插入,更新和删除数据的异常情况。

解决异常的方法:

首先我们需要确认造成异常的原因:主属性仓库名对于候选键(管理员,物品名)是部分依赖的关系,这样就有可能导致上面的异常情况。人们在 3NF 的基础上进行了改进,提出了 BCNF,也叫做巴斯 - 科德范式,它在 3NF 的基础上消除了主属性对候选键的部分依赖或者传递依赖关系。

根据 BCNF 的要求,我们需要把仓库管理关系 warehouse_keeper 表拆分成下面这样:

仓库表:(仓库名,管理员)
库存表:(仓库名,物品名,数量)

这样就不存在主属性对于候选键的部分依赖或传递依赖,上面数据表的设计就符合 BCNF。

反范式设计

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

我们在之前已经了解了越高阶的范式得到的数据表越多,数据冗余度越低。但有时候,我们在设计数据表的时候,还需要为了性能和读取效率违反范式化的原则。反范式就是相对范式化而言的,换句话说,就是允许少量的冗余,通过空间来换时间。

如果我们想对查询效率进行优化,有时候反范式优化也是一种优化思路。

举例说明
使用到的表如下:
商品评论表 product_comment,对应的字段名称及含义如下:
在这里插入图片描述
用户表
在这里插入图片描述

下面,我们就用这两张表模拟一下反范式优化。

生成实验数据:两张表都插入百万条数据

下面是给用户表随机生成 100 万用户的代码:

// 存储过程定义
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
// 存储过程调用
call insert_many_user(10000, 1000000);

以下是创建随机的 100 万条商品评论的存储过程:

// 定义存储过程
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);

反范式优化实验对比

如果我们想要查询某个商品 ID,比如 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.395 秒,查询效率并不高。

这是因为在实际生活中,我们在显示商品评论的时候,通常会显示这个用户的昵称,而不是用户 ID,因此我们还需要关联 product_comment 和 user 这两张表来进行查询。当表数据量不大的时候,查询效率还好,但如果表数据量都超过了百万量级,查询效率就会变低。这是因为查询会在 product_comment 表和 user 表这两个表上进行聚集索引扫描,然后再嵌套循环,这样一来查询所耗费的时间就有几百毫秒甚至更多。对于网站的响应来说,这已经很慢了,用户体验会非常差。

如果我们想要提升查询的效率,可以允许适当的数据冗余,也就是在商品评论表中增加用户昵称字段,在 product_comment 数据表的基础上增加 user_name 字段,就得到了 product_comment2 数据表。

这样一来,只需单表查询就可以得到数据集结果:

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

优化之后只需要扫描一次聚集索引即可,运行时间为 0.039 秒,查询时间是之前的 1/10。 你能看到,在数据量大的情况下,查询效率会有显著的提升。

反范式存在的问题 & 适用场景

从上面的例子中可以看出,反范式可以通过空间换时间,提升查询的效率,但是反范式也会带来一些新问题。

在数据量小的情况下,反范式不能体现性能的优势,可能还会让数据库的设计更加复杂。比如采用存储过程来支持数据的更新、删除等额外操作,很容易增加系统的维护成本。(比如上面例子中在商品评论表中增加用户名字段,如果用户名发生了改变,那么商品评论表中同一个用户的评论记录中的用户名都需要改变)

比如用户每次更改昵称的时候,都需要执行存储过程来更新,如果昵称更改频繁,会非常消耗系统资源。

那么反范式优化适用于哪些场景呢?

在现实生活中,我们经常需要一些冗余信息,比如订单中的收货人信息,包括姓名、电话和地址等。每次发生的订单收货信息都属于历史快照,需要进行保存,但用户可以随时修改自己的信息,这时保存这些冗余信息是非常有必要的。

当冗余信息有价值或者能大幅度提高查询效率的时候,我们就可以采取反范式的优化。

此外反范式优化也常用在数据仓库的设计中,因为数据仓库通常存储历史数据,对增删改的实时性要求不强,对历史数据的分析需求强。这时适当允许数据的冗余度,更方便进行数据分析。

我简单总结下数据仓库和数据库在使用上的区别:

数据库设计的目的在于捕获数据,而数据仓库设计的目的在于分析数据;
数据库对数据的增删改实时性要求强,需要存储在线的用户数据,而数据仓库存储的一般是历史数据;
数据库设计需要尽量避免冗余,但为了提高查询效率也允许一定的冗余度,而数据仓库在设计上更偏向采用反范式设计。

反范式化设计总结

今天我们讲了 BCNF,它是基于 3NF 进行的改进。你能看到设计范式越高阶,数据表就会越精细,数据的冗余度也就越少,在一定程度上可以让数据库在内部关联上更好地组织数据。但有时候我们也需要采用反范进行优化,通过空间来换取时间。

范式本身没有优劣之分,只有适用场景不同。没有完美的设计,只有合适的设计,我们在数据表的设计中,还需要根据需求将范式和反范式混合使用。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux中的动静态库

目录 一、静态库 (1)静态库的优缺点: (2)Linux下静态库的创建和执行 1.直接编译​编辑 2.指定路径和库名 3.用LIBRARY_PATH环境变量来配置路径 二、动态库 (1)动态库的优缺点 &#xff…

达梦数据库将DMHR模式下的表(迁移)导出为EXCEL文件

数据库迁移工具(Data Transfer Service)位于/dm8/tool/dts.其中/dm8是数据库安装目录。 在创建数据库时我们如果勾选了 “创建示例库DMHR(R)”,数据库实例中就带有这个数据库。 这里是用MobaXterm客户端远程控制ip地址为192.168.148.130的虚…

自动化测试环境搭建--Linux内网环境【实操经验】

环境信息 Python版本3.6.8 Jenkins版本2.346.3 Allure版本 2.13.2 环境准备 一、Python需要安装库 allure-pytest (2.13.2) pytest (7.0.1) jsonpath (0.82.2) pytest-html (3.2.0) pytest-ordering (0.6) …

数据分析-Pandas数据y轴双坐标设置

数据分析-Pandas数据y轴双坐标设置 数据分析和处理中,难免会遇到各种数据,那么数据呈现怎样的规律呢?不管金融数据,风控数据,营销数据等等,莫不如此。如何通过图示展示数据的规律? 数据表&…

计算机网络——概述

计算机网络——概述 计算机网络的定义互连网(internet)互联网(Internet)互联网基础结构发展的三个阶段第一个阶段——APPANET第二阶段——商业化和三级架构第三阶段——全球范围多层次的ISP结构 ISP的作用终端互联网的组成边缘部分…

EasyExcel的使用

1、简介 在日常项目开发中,经常会遇到解析excel文件中的数据、或者将某些数据导出成excel文件的需求,目前Java解析、生成Excel比较有名的框架有Apache POI、jxl,但是它们都存在一个严重的问题就是非常的耗内存。POI有一套Sax模式的API可以在一…

【k8s 高级调度--亲和力/反亲和力】

1、亲和性/反亲和性介绍 nodeSelector 提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上。 亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有: 亲和性、反亲和性语言的表达能力更强。nodeSelector 只能选择拥有所有指定…

数电票又增新票种 百望云率先推出机动车数电票解决方案

经过两年多的试点推广,数电票工程已经覆盖全国大部分省区市(含计划单列市),随着系统建设的深入,不仅是应用功能逐渐完善,对票种的支持也走向全面。 2023年11月天津市正式开启机动车数电票的开具功能&#x…

2024高频前端面试题 Vue2 和 Vue3 篇

* Vue2 和 Vue3的区别: 1)双向数据绑定原理的区别 2)根节点的不同 Vue2只能一个根节点 Vue3在组件中可以放置多个根节点 3)Vue3中采用composition API vue2:采用的选项型API(opsition API) vue3:采用的组合型API(composition A…

分享76个Html杂七杂八模板,总有一款适合您

分享76个Html杂七杂八模板,总有一款适合您 76个Html杂七杂八模板下载链接:https://pan.baidu.com/s/1ZQ16JmRiwgxlvDq9Vi41uA?pwd8888 提取码:8888 学习知识费力气,收集整理更不易。知识付费甚欢喜,为咱码农谋福…

网络进程:广播、组播、流式域、报式域(套接字)

1.广播 1.1广播发送端模型(类似UDP客户端) 程序代码&#xff1a; #include<myhead.h> int main(int argc, const char *argv[]) {//创建套接字int sfdsocket(AF_INET,SOCK_DGRAM,0);if(sfd-1){perror("socket error");return -1;}//将套接字设置成允许广播i…

cmd模式下启动mysql

1.打开cmd输入services.msc&#xff0c;找到MYSQL&#xff0c;右击属性&#xff0c;找到可执行文件路径&#xff0c;加载到环境变量。 2.打开cmd&#xff0c;启动MYSQL&#xff1a;输入net start mysql; 3.登陆MYSQL&#xff0c;需要管理权限&#xff1b; 输入&#xff1a;my…

【OBS】stream-labs-desktop 编译运行采坑全攻略

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ 安装yarn安装 2️⃣ 安装依赖库&#xff1a;yarn installcertificate has expiredelectronnpm:25.9.3 couldnt be built successfully 3️⃣ 启动desktop项目编译调试模式启动启动缓慢问题 4️⃣ 打包&#x1f6ec; 文章小结…

sentinel docker 基础配置学习

1&#xff1a;去官网下载 Releases alibaba/Sentinel GitHub 2&#xff1a;保存到linux 3&#xff1a;编写dockerfile FROM openjdk:8-jreLABEL authors"xxx" #第二步创建一个文件夹Z RUN mkdir /app #第三步复制jar 到app 下 COPY xxxxxx-1.8.7.jar /app/#第四…

动态规划(算法竞赛、蓝桥杯)--背包DP求具体方案

1、B站视频链接&#xff1a;E20 背包DP 求具体方案_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N1010; int v[N],w[N]; int f[N][N],p[N][N];int main(){int n,m;cin>>n>>m;for(int i1;i<n;i)cin>>v[i]>>w[i…

云计算 3月5号 (DNS域名解析及部署)

DNS域名解析服务 1.DNS介绍 DNS 是域名系统 (Domain Name System) 的缩写&#xff0c;是因特网的一项核心服务&#xff0c;它作为可以将域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便的访问互联网&#xff0c;而不用去记住能够被机器直接读取的IP数串。…

UD效果广告

1.定义 全称Unidesk&#xff0c;是由阿里旗下大数据运营平台“阿里妈妈”推出的数字营销引流平台。UD投放将其他媒体的流量通过相关的广告创意导入到天猫店铺。 2.UD投放优化技巧 &#xff08;1&#xff09;不起量排查&#xff1a; 可以从账户问题、计划数量不足、计划设置…

c语言经典测试题11

1.题1 #include <stdio.h> int main() { int a[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, *p a 5, *q NULL; *q *(p5); printf("%d %d\n", *p, *q); return 0; }上述代码的运行结果是什么呢&#xff1f; 我们来分析一下&#xff1a;我们创建了一个数…

美易官方《盘前:美国股指期货温和走低》

美国股指期货在盘前交易中温和走低&#xff0c;市场情绪在美联储主席鲍威尔即将作证前显得谨慎。投资者对即将公布的证词内容充满期待&#xff0c;以寻求对美联储未来货币政策的更多线索。 鲍威尔即将在国会作证&#xff0c;这是市场关注的焦点事件之一。他的证词可能会对美元汇…

删除有序链表中重复的数字Ⅱ

题目 题目链接 删除有序链表中重复的元素-II_牛客题霸_牛客网 题目描述 代码实现 class Solution { public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param head ListNode类 * return ListNode类*/ListNod…