字节2面:为了性能,你会违反数据库三范式吗?

大家好,我是猿java

数据库的三大范式,它是数据库设计中最基本的三个规范,那么,三大范式是什么?在实际开发中,我们一定要严格遵守三大范式吗?这篇文章,我们一起来聊一聊。

1. 三大范式

1. 第一范式(1NF,确保每列保持原子性)

第一范式要求数据库中的每个表格的每个字段(列)都具有原子性,即字段中的值不可再分割。换句话说,每个字段只能存储一个单一的值,不能包含集合、数组或重复的组。

如下示例: 假设有一个学生表 Student,结构如下:

学生ID 姓名 电话号码
1 张三 123456789, 987654321
2 李四 555555555

在这个表中,电话号码字段包含多个号码,违反了1NF的原子性要求。为了满足1NF,需要将电话号码拆分为单独的记录或创建一个新的表。

满足 1NF后的设计:

学生表 Student

学生ID 姓名
1 张三
2 李四

电话表 Phone

电话ID 学生ID 电话号码
1 1 123456789
2 1 987654321
3 2 555555555

1.2 第二范式(2NF,确保表中的每列都和主键相关)

第二范式要求满足第一范式,并且消除表中的部分依赖,即非主键字段必须完全依赖于主键,而不是仅依赖于主键的一部分。这主要适用于复合主键的情况。

如下示例:假设有一个订单详情表 OrderDetail,结构如下:

订单ID 商品ID 商品名称 数量 单价
1001 A01 苹果 10 2.5
1001 A02 橙子 5 3.0
1002 A01 苹果 7 2.5

在上述表中,主键是复合主键 (订单ID, 商品ID)商品名称单价只依赖于复合主键中的商品ID,而不是整个主键,存在部分依赖,违反了2NF。

满足 2NF后的设计:

订单详情表 OrderDetail

订单ID 商品ID 数量
1001 A01 10
1001 A02 5
1002 A01 7

商品表 Product

商品ID 商品名称 单价
A01 苹果 2.5
A02 橙子 3.0

1.3 第三范式(3NF,确保每列都和主键列直接相关,而不是间接相关)

第三范式要求满足第二范式,并且消除表中的传递依赖,即非主键字段不应依赖于其他非主键字段。换句话说,所有非主键字段必须直接依赖于主键,而不是通过其他非主键字段间接依赖。

如下示例:假设有一个员工表 Employee,结构如下:

员工ID 员工姓名 部门ID 部门名称
E01 王五 D01 销售部
E02 赵六 D02 技术部
E03 孙七 D01 销售部

在这个表中,部门名称依赖于部门ID,而部门ID依赖于主键员工ID,形成了传递依赖,违反了3NF。

满足3NF后的设计:

员工表 Employee

员工ID 员工姓名 部门ID
E01 王五 D01
E02 赵六 D02
E03 孙七 D01

部门表 Department

部门ID 部门名称
D01 销售部
D02 技术部

通过将部门信息移到单独的表中,消除了传递依赖,使得数据库结构符合第三范式。

最后,我们总结一下数据库设计的三大范式:

  • 第一范式(1NF): 确保每个字段的值都是原子性的,不可再分。
  • 第二范式(2NF): 在满足 1NF的基础上,消除部分依赖,确保非主键字段完全依赖于主键。
  • 第三范式(3NF): 在满足 2NF的基础上,消除传递依赖,确保非主键字段直接依赖于主键。

2. 破坏三范式

在实际工作中,尽管遵循数据库的三大范式(1NF、2NF、3NF)有助于提高数据的一致性和减少冗余,但在某些情况下,为了满足性能、简化设计或特定业务需求,我们可能需要违反这些范式。

下面列举了一些常见的破坏三范式的原因及对应的示例。

2.1 性能优化

在高并发、大数据量的应用场景中,严格遵循三范式可能导致频繁的联表查询,增加查询时间和系统负载。为了提高查询性能,设计者可能会通过冗余数据来减少联表操作。

假设有一个电商系统,包含订单表 Orders 和用户表 Users。在严格 3NF设计中,订单表只存储 用户ID,需要通过联表查询获取用户的详细信息。

但是,为了查询性能,我们通常会在订单表中冗余存储 用户姓名用户地址等信息,因此,查询订单信息时无需联表查询 Users 表,从而提升查询速度。

破坏 3NF后的设计:

订单ID 用户ID 用户姓名 用户地址 订单日期 总金额
1001 U01 张三 北京市 2023-10-01 500元
1002 U02 李四 上海市 2023-10-02 300元

2.2 简化查询和开发

严格规范化可能导致数据库结构过于复杂,增加开发和维护的难度,为了简化查询逻辑和减少开发复杂度,我们也可能会选择适当的冗余。

比如,在内容管理系统(CMS)中,文章表 Articles 和分类表 Categories 通常是独立的,如果频繁需要显示文章所属的分类名称,联表查询可能增加复杂性。因此,通过在 Articles 表中直接存储 分类名称,可以简化前端展示逻辑,减少开发工作量。

破坏 3NF后的设计:

文章ID 标题 内容 分类ID 分类名称
A01 文章一 ... C01 技术
A02 文章二 ... C02 生活

2.3 报表和数据仓库

在数据仓库和报表系统中,通常需要快速读取和聚合大量数据。为了优化查询性能和数据分析,可能会采用冗余的数据结构,甚至使用星型或雪花型模式,这些模式并不完全符合三范式。

在销售数据仓库中,为了快速生成销售报表,可能会创建一个包含维度信息的事实表。

破坏 3NF后的设计:

销售ID 产品ID 产品名称 类别 销售数量 销售金额 销售日期
S01 P01 手机 电子 100 50000元 2023-10-01
S02 P02 书籍 教育 200 20000元 2023-10-02

在事实表中直接存储 产品名称类别,避免了需要联表查询维度表,提高了报表生成的效率。

2.4 特殊业务需求

在某些业务场景下,可能需要快速响应特定的查询或操作,这时通过适当的冗余设计可以满足业务需求。

比如,在实时交易系统中,为了快速计算用户的账户余额,可能会在用户表中直接存储当前余额,而不是每次交易时都计算。

破坏 3NF后的设计:

用户ID 用户名 当前余额
U01 王五 10000元
U02 赵六 5000元

在交易记录表中存储每笔交易的增减,但直接在用户表中维护 当前余额,避免了每次查询时的复杂计算。

2.5 兼顾读写性能

在某些应用中,读操作远多于写操作。为了优化读性能,可能会通过数据冗余来提升查询速度,而接受在数据写入时需要额外的维护工作。

社交媒体平台中,用户的好友数常被展示在用户主页上。如果每次请求都计算好友数量,效率低下。可以在用户表中维护一个 好友数 字段。

破坏3NF后的设计:

用户ID 用户名 好友数
U01 Alice 150
U02 Bob 200

通过在 Users 表中冗余存储 好友数,可以快速展示,无需实时计算。

2.6 快速迭代和灵活性

在快速发展的产品或初创企业中,数据库设计可能需要频繁调整。过度规范化可能导致设计不够灵活,影响迭代速度。适当的冗余设计可以提高开发的灵活性和速度。

一个初创电商平台在初期快速上线,数据库设计时为了简化开发,可能会将用户的收货地址直接存储在订单表中,而不是单独创建地址表。

破坏3NF后的设计:

订单ID 用户ID 用户名 收货地址 订单日期 总金额
O1001 U01 李雷 北京市海淀区... 2023-10-01 800元
O1002 U02 韩梅梅 上海市浦东新区... 2023-10-02 1200元

这样设计可以快速上线,后续根据需求再进行规范化和优化。

2.7 降低复杂性和提高可理解性

有时,过度规范化可能使数据库结构变得复杂,难以理解和维护。适度的冗余可以降低设计的复杂性,提高团队对数据库结构的理解和沟通效率。

在一个学校管理系统中,如果将学生的班级信息独立为多个表,可能增加理解难度。为了简化设计,可以在学生表中直接存储班级名称。

破坏3NF后的设计:

学生ID 姓名 班级ID 班级名称 班主任
S01 张三 C01 三年级一班 李老师
S02 李四 C02 三年级二班 王老师

通过在学生表中直接存储 班级名称班主任,减少了表的数量,简化了设计。

3. 总结

本文,我们分析了数据库的三范式以及对应的示例,它是数据库设计的基本规范。但是,在实际工作中,为了满足性能、简化设计、快速迭代或特定业务需求,我们很多时候并不会严格地遵守三范式。

所以说,架构很多时候都是业务需求、数据一致性、系统性能、开发效率等各种因素权衡的结果,我们需要根据具体应用场景做出合理的设计选择。

4. 学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

相关推荐
TPBoreas4 分钟前
Spring Boot 内置工具类
java·spring boot·后端
浪里渔夫10 分钟前
Lombok @Data无法 import 导入的问题解决办法
java·lombok
w36250126618 分钟前
Java NIO
java·开发语言·nio
Java 第一深情22 分钟前
面试题解,JVM中的“类加载”剖析
java·jvm
阿年、嗯啊1 小时前
MySQL和Hive中的行转列、列转行
数据库·hive·mysql·侧窗·行专列、列转行·hive侧窗列转行·构造map数据结构
m0_748244962 小时前
Tomcat 的安装(详细教程)
java·tomcat
一弓虽2 小时前
java 基础学习——java 异常详细介绍
java·学习·throw
王stone2 小时前
Mysql运维利器之备份恢复-xtrabackup 安装
数据库·mysql
CharlesC++2 小时前
JAVA类和对象练习
java·开发语言