超详细:三大范式和反范式设计详解

目录

1、三大范式

第一范式: 列不可再分 。

第二范式: 行可以唯一区分

第三范式:确保数据的完整性、减少数据冗余和避免更新异常。

反方式模式 

实验数据:模拟两张百万量级的数据表

反范式优化实验对比

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

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

使用反范式好处:

总结:

‌三大范式具体应用场景‌:

    反范式的优点:

    反范式的缺点:


1、三大范式

首先,数据库设计我们要遵循3大范式,这套设计流程可以避免很对的问题,如数据冗余、重复数据等等。这是在设计层面前辈给我们总结出来的经验,对我们设计数据库非常有帮助。

  1. 减少数据冗余‌:通过遵循第一范式、第二范式和第三范式,可以最大限度地减少数据冗余,使得每个数据项只存储一次,避免了数据的重复存储。这样可以节省存储空间,并减少数据更新和维护的工作量‌ 。

  2. 提高数据一致性‌:三范式通过消除数据依赖关系,确保了数据的一致性。每个数据项只依赖于主键,避免了数据更新时可能出现的异常情况,保证了数据的完整性‌ 。

  3. 提高数据可维护性‌:三范式使得数据库结构更加清晰和易于维护。通过合理的表结构设计,减少了数据修改的复杂性,使得数据库更容易进行修改和扩展‌ 。

  4. 优化查询性能‌:通过合理的表结构设计,三范式可以提高查询的效率和响应速度,提升数据库的性能。避免了在查询时需要多次关联多个表的情况,减少了查询的复杂度‌ 。

  5. 增强数据完整性‌:三范式通过消除部分依赖和传递依赖,确保了数据的完整性。每个非主键字段只依赖于主键,避免了数据更新时可能出现的异常情况,保证了数据的准确性‌ 


 比如我们不使用三大范式直接存储数据:

  如果直接这样存储在数据库的一列中 即不好查找也不方便辨认 还容易混淆 数据冗余等等问题

这样一对比就可以看出来 明显阿萨姆奶茶的辨认度要高而且也非常容易辨认 ,所有的域都应该是原子性的,即数据库表的每一列都是不可分割的原子数据项,不能是集合、数组等非原子数据项。第一范式要求表中的每个字段都代表一个不可分割的属性值

简单的说就是一个列且只对应一个属性。

第一范式: 列不可再分 。

 将信息进行分析和拆解,把字段一个个的拆分出来,不同的字段放不同的信息 。

 保证字段的原子性,一个字段说一个事情,这就符合第一范式 

如下图就是符合第一范式了:

  但是只满足第一范式也有问题: 

例如普通的可乐3元 但是无糖的也是3元 你只说要可乐 那你怎么区分呢 都是可乐

如果没有id   你怎么区分怎么知道哪个是无糖的哪个是常规的呢?

 如果发生这种重复冗余数据 是不是区别不了 这个时候 第二范式就来了

第二范式: 行可以唯一区分

在第一范式的基础上,非主键属性必须完全依赖于候选键。这意味着在第二范式中,表中的每个实例或记录必须可以被唯一地区分选取一个能区分每个实体的属性属性组作为实体的唯一标识。

解析:

因为重复的数据查找和区分比较困难,这时候我们就可以给这数据加个id 加个主键 ,这个主键就是唯一区分这条数据的一个键 通过这个唯一的主键 我们就可以明确的确定是那条数据 这个就叫做第二范式 行可以唯一区分。 

就和我们人的名字一样 当叫到你名字的时候就知道是在叫你

像这样就符合第一、二范式了 。但是只满足第一二范式就可以了吗?那这个时候我们商品的类型、商品的热度、排名什么什么的,很多种数据。

如果都直接存放在这一个表中:

  • 第一 他这个表的数据会有一个冗余重复 
  • 第二不方便修改,如果我们这个表有10亿数据的话 我们要修改这个类型的名称 是不是每个都要修改 修改10亿次 极其的不方便和麻烦
  • 第三而且这么多的数据也不好管理

那么怎么 确保数据的完整性、减少数据冗余和避免更新异常呢? 第三范式来了~

第三范式:确保数据的完整性、减少数据冗余和避免更新异常。

数据库三大范式是数据库设计的基本原则:

  • 确保数据的完整性、减少数据冗余和避免更新异常。

可以看到 goods表 这里面的 type(类型)列 不是和主键直接相关 而是间接相关的一个小团体

解析: 

就比如我们的商品类型,我们可以直接将它抽离出主表,变成一个独立的从表 。然后通过这个独立的从表的外键的方式进行一个关联引用 ,这种拆表的方式就叫做第三范式 。 我们要使用的时候只需要在主表中引入 从表的主键id 进行一个关联即可。

1、我们直接将 type 列从goods主表进行抽离  然后变成从表的一个id

2、抽离后形成一个产品类别表(type):

 3、可以看到type表中只有 typeId (产品id)  和一个 typeName (产品名称)  

 

如果需要用的时候我们直接进行一个连表查询即可:

 SELECT g.*,t.typeName FROM `goods` g left join `type` t on g.typeId = t.typeId

这样抽离出一个 独立的从表 之后假如我们要修改他的 typeName (产品名称)是不是只需要在type从表中修改一次typeName即可     这样就达到了一个批量次修改的效果 只需要一次操作就能完成所有的修改避免了数据冗余的可能。

这样一来一套满足三大范式的规范就完成了!! 

这就是数据库的三大范式也是数据库设计的基本原则,确保数据的完整性、减少数据冗余和避免更新异常。

虽然三大范式很符合规范 但是每次都必须要关联从表,进行连表查询比较繁琐麻烦,这时候我们就可以使用空间换时间,我们将主表中常用且不易改变的数据

反方式模式 

我们在之前已经了解了越高阶的范式得到的数据表越多数据冗余度越低。但有时候,我们在设计数据表的时候,还需要为了性能和读取效率违反范式化的原则

反范式就是相对范式化而言的,换句话说,就是 允许少量的冗余通过空间来换时间

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

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

比如我们想要查询某个商品的前 1000 条评论,会涉及到两张表。

商品评论表 product_comment,对应的字段名称及含义如下:

用户表 user,对应的字段名称及含义如下:

 

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

实验数据:模拟两张百万量级的数据表

为了更好地进行 SQL 优化实验,我们需要给用户表和商品评论表随机模拟出百万量级的数据。我们可以通过存储过程来实现模拟数据。

CREATE TABLE `user`  (`user_id` int(11) NOT NULL,`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`create_time` datetime(0) NOT NULL,PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `product_comment`  (`comment_id` int(11) NOT NULL AUTO_INCREMENT,`product_id` int(11) NOT NULL,`comment_text` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`comment_time` datetime(0) NOT NULL,`user_id` int(11) NOT NULL,PRIMARY KEY (`comment_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10000 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

下面是给用户表随机生成 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

我用 date_start 变量来定义初始的注册时间,时间为 2017 年 1 月 1 日 0 点 0 分 0 秒,然后用 date_temp 变量计算每个用户的注册时间,新的注册用户与上一个用户注册的时间间隔为 60 秒内的随机值。然后使用 REPEAT … UNTIL … END REPEAT 循环,对 max_num 个用户的数据进行计算。在循环前,我们将 autocommit 设置为 0,这样等计算完成再统一插入,执行效率更高。

call insert_many_user(10000, 1000000)

可以看到这里我插入1000000 数据使用了61秒 还是比较慢的

接着我们再来给商品评论表 product_comment 随机生成 100 万条商品评论。这里我们设置为给某一款商品评论,比如 product_id=10001。评论的内容为随机的 20 个字母。以下是创建随机的 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

同样的,我用 date_start 变量来定义初始的评论时间。这里新的评论时间与上一个评论的时间间隔还是 60 秒内的随机值,商品评论表中的 user_id 为随机值。我们使用 REPEAT … UNTIL … END REPEAT 循环,来对 max_num 个商品评论的数据进行计算。

--  起始值和最大循环次数
call insert_many_product_comments(1000, 1000000)

 可以看到评论表插入用了80秒


反范式优化实验对比

如果我们想要查询某个商品 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.024s 

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

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

创建 `product_comment2` 表

CREATE TABLE `product_comment2`  (`comment_id` int(11) NOT NULL AUTO_INCREMENT,`product_id` int(11) NOT NULL, `comment_text` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`comment_time` datetime(0) NOT NULL,`user_id` int(11) NOT NULL,	PRIMARY KEY (`comment_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10000 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

 插入数据: 


CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_many_product_comments2`(IN start INT(10), IN max_num INT(10))
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE date_start DATETIME DEFAULT ('2024-11-11 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_product_comments2(1000, 10000)

测试:

--  查询前10000条数据
SELECT comment_text, comment_time, user_name FROM product_comment2 WHERE product_id = 10001 ORDER BY comment_id DESC LIMIT 1000;

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



 

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


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

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

在数据量小的情况下,反范式不能体现性能的优势,可能还会让数据库的设计更加复杂。比如采用存储过程来支持数据的更新、删除等额外操作,很容易增加系统的维护成本。

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


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

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

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

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


使用反范式好处:

  •  范式化的数据库更新起来更加快
  •  范式化之后,只有很少的重复数据,只需要修改更少的数据
  •  范式化的表更小,可以在内存中执行
  •  很少的冗余数据,在查询的时候需要更少的distinct或者group by语句。

总结:

‌三大范式具体应用场景‌:

  • 第一范式‌:适用于所有字段的值都是不可分割的原子值的情况。例如,将地址信息拆分成街道、城市、省份等单独的字段。
  • 第二范式‌:适用于非主键列完全依赖于主键的情况。例如,在联合主键的情况下,非主键列不能只依赖于主键的一部分,需要将相关数据拆分到不同的表中。
  • 第三范式‌:适用于消除非主键字段之间的传递依赖。例如,将部门信息和员工信息分开存储,避免员工表中包含部门信息,确保每个非主键字段只依赖于主键‌ 。

 通过遵循三范式,可以构建高效、可维护的数据库结构,减少数据冗余,提高数据一致性,优化查询性能,从而提升数据库的整体性能和可靠性

    反范式的优点:

  •  可以避免关联,因为所有的数据几乎都可以在一张表上显示;
  •  可以设计有效的索引;

    反范式的缺点:

  •  表格内的冗余较多,删除数据时候会造成表有些有用的信息丢失。

 


具体开发中可以根据自己的情况自行选择设计模式开发出更高效实用的数据库

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

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

相关文章

新标准大学英语综合教程1课后习题答案PDF第三版

《新标准大学英语(第三版)综合教程1 》是“新标准大学英语(第三版)”系列教材之一。本书共包含6个单元,从难度和话题上贴近大一上学生的认知和语言水平,包括与学生个人生活领域和社会文化等相关内容&#x…

Llama 3.2-Vision 多模态大语言模型

1. 引言 Llama 3.2-Vision多模态大型语言模型(文本 图像)是一个图像推理生成模型,按照官方的说法,在常见行业基准测试上,其性能优于许多可用的开源和闭源多模态模型。Llama 3.2-Vision有两个版本,一个是11B (7.9G),另…

OpenObserve云原生平台指南:在Ubuntu上快速部署与远程观测

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动OpenObserve容器4. 本地访问测试5. 公网访问本地部署的OpenObserve5.1 内网穿透工具安装5.2 创建公网地址 6. 配置固定公网地址 前言 本文主要介绍如何在Linux系统使用Docker快速本地化部署OpenObserve云原生可…

隧道论文阅读2-采用无人融合扫描数据的基于深度学习的垂直型隧道三维数字损伤图

目前存在的问题: 需要开发新的无人测量系统测量垂直隧道图像数据量巨大,基于深度学习完成损伤评估跟踪获取图像位置的困难,对大型基础设施感兴趣区域(roi)的2d和3d地图建立进行了研究,对整个目标结构的损伤定位仍然具有挑战性。为…

【从VAE到LDM】Variational Auto Encoder原理以及关于Latent Diffusion的思考

论文链接:High-Resolution Image Synthesis with Latent Diffusion Models 官方实现:CompVis/latent-diffusion、CompVis/stable-diffusion 视频讲解:一个视频看懂VAE的原理以及关于latent diffusion的思考 前言 目前的扩散模型范式基本上都…

1111fxh,MYSQL加锁规则

怎么查看一个事务中对索引的加锁情况 -- 这条语句可以看到事务执行过程中加了哪些锁 select * from performance_schema.data_locks X是next-key lock 混合锁 X,REC_NOT_GAP就是行锁 X,GAP是间隙锁 可重复读下的加锁规则 2原则2优化 1.加锁的基本单位是next-key lock,即行…

mysql中数据不存在却查询到记录?

前言 首先看下面的查询语种 select * from AudioKnowledgeChatInfo where AudioId297795550566600706; 查询结果如下 看到上面的查询结果,是不是一脸懵?这audioId明显不对啊,怎么查询到了? 原因剖析 首先我们来看看数据库表…

拿不下总统之位,那就用热加载拿下验证码识别与爆破好了!

大家好,这里是在总统选举中惜败的超级牛 虽然没能拿下阿美利卡总统之位 但是牛牛的热加载功能,却能轻松拿下验证码的识别与爆破 验证码一般会在注册、登录等功能,用来防止自动化工具的攻击。一般的验证码生成过程如下图所示: …

闯关leetcode——202. Happy Number

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/happy-number/description/ 内容 Write an algorithm to determine if a number n is happy. A happy number is a number defined by the following process: Starting with any positive inte…

如何使用 Web Scraper API 高效采集 Facebook 用户帖子信息

目录 前言一、什么是Web Scraper API二、Web Scraper API 的优势:三、Web Scraper API 适用场景四、实践案例目标需求视频讲解1、选择Web Scraper API2、登录注册3、进入用户控制面板4、选择API5、触发数据收集 API6、获取爬虫结果7、分析爬虫结果(1&…

json转excel,读取json文件写入到excel中【rust语言】

一、rust代码 将json文件写入到 excel中。(保持json :key原始顺序) use indexmap::IndexMap; use serde::Deserialize; use serde_json::{Value, from_str}; use std::error::Error; use std::io::{self, Write}; use std::path::{Path}; u…

2024年下半年系统规划与管理师论文真题

试题一、论IT服务规划设计 IT服务规划设计处于整个IT服务生命周期中的前端,可以帮助IT服务供方了解客户的需求并对其进行全面的需求分析,规划设计的范围不仅包括新的服务,还包括服务连续性保障服务水平的满足和对标准、规则的遵从&#xff0…

无人机动力测试台如何快速外接第三方传感器

前言 动力测试台对于测试动力系统的拉力、扭矩、RPM 和效率至关重要。将传感器集成到您的测试中增加了另一层优化,可以将您的性能提升到一个新的水平。 在无人驾驶行业中,有充分的证据表明,从外部传感器收集数据可能具有挑战性。为了解决这…

金蝶云星空与旺店通数据集成解决方案实例

金蝶云星空与旺店通旗舰奇门的数据集成案例分享 在企业日常运营中,数据的高效流转和准确对接是确保业务顺畅运行的关键。本文将聚焦于一个具体的系统对接集成案例:如何将金蝶云星空中的直接调拨单数据无缝集成到旺店通旗舰奇门平台。 本次集成方案命名…

gorm使用注意事项

1. 使用updates更新字段为0或""时 在updates更新时,会默认将数值为0、字符串为""等忽略掉,不会更新;比如 db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false, Games: 0, Friend: &qu…

docker compose - 设置名字

只使用 docker compose up 启动容器,默认名字为当前文件夹的名字 设置 project-name,docker 客户端会显示设置的名字,方便区分 docker compose --project-name webtest up错误: docker compose up --project-name webtest 效果…

破解反向代购复杂挑战,一站式简化逆向海淘购物

在全球化日益加深的今天,反向代购行业迎来了前所未有的发展机遇,同时也伴随着一系列挑战。这些企业不仅需要穿梭于世界各地的商场与网店,搜罗各式各样的商品,还要应对商品众多、客户下单繁杂以及客户细分需求多样化的现实问题。想…

户型超赞!招商“超级大城”再腾飞!三期新品全面升级!即将首开!

2024年9月底,美联储降息带来整体政策方向全面转变楼市组合大招密集落地,力度非同寻常。先是降息、降准、降存量房贷利率等货币宽松政策,到国家首提“止跌回稳”再到上海、深圳等一线城市连夜出台限购优化政策。在利好扶持重磅加码&#xff0c…

Web3 游戏周报(11.03 - 11.09)

回顾上周的区块链游戏概况,查看 Footprint Analytics 与 ABGA 最新发布的数据报告。 【11.03 - 11.09】Web3 游戏行业动态: Ton Accelerator 推出名为「Synergy」的 500 万美元计划,旨在推动跨链创新,创造 TON 用户与 EVM 网络适应…

Kafka面试题解答(一)

1.kafka消息发送的流程? 生产者: 在消息发送的过程中涉及到了两个线程:main线程和sender线程。在main线程中创建了一个双端队列RecordAccumulator(默认32m)。main线程将消息发送到RecordAccumulator,sender线程不断地…