一站式了解数据库三大范式(库表设计基础)

引言

作为后端开发者,项目初期进行库表设计的时候,如果光凭经验而没有一套合适的方法论,大概率项目最后会变成一个难以维护的"史山"。那么我们就来简单讲讲数据库表设计的三大范式,尽量都让大家听懂,我们的最终目的是大家掌握应对各种业务场景设计的思维框架

第一范式

第一范式:确保关系中的所有属性(列)都是不可再分的原子性值。

具体含义:

  • 表中的每一个字段都不可再分。
  • 不允许在同一列中存储多个值,例如,一个"联系方式"字段不能包含"电话号码, 电子邮件地址"
  • 不允许在表中创建"重复组"------即一个记录(行)中不能有多个重复的字段,例如"产品1, 产品2, 产品3"

举个例子来回答,不允许在同一列中存储多个值:比如说地址这个列,你不能存储成广东省广州市xx区xx街道....这样整个字符串,这样地址还可以再进行拆分,而是你要拆成直到不能再进行拆分的原子字段,比如省,市,区等等。

一个记录(行)中不能有多个重复的字段,比如说课程里面你不能同时记录数学,语文等等,而是应该分开多个行进行记录

第二范式

第二范式:要求每一列(非主键字段)都完全依赖于主键

具体含义:

  • 如果一个表的主键是联合主键 (由多个列组成),那么所有非主键列都必须依赖于整个联合主键。
  • 如果任何一个非主键列只依赖于联合主键中的部分列,则违反了 2NF。这会导致"部分函数依赖"。

举个例子来解释第二范式,订单表里面就不能出现商品的库存信息,因为商品的库存信息依赖于商品的ID而不依赖于订单ID。那么简单来说就是表里面的每个列都必须完全依赖于主键,不管这个主键是联合主键还是独立主键,不能出现部分依赖的情况,否则就要拆分部分依赖的列到其他表里面去。

再举个违反 2NF 的例子: (假设 订单ID产品ID 组成联合主键)

订单ID 产品ID 订单日期 产品名称 产品价格
1001 A101 2025-12-15 笔记本电脑 8000
1001 B205 2025-12-15 鼠标 150
1002 A101 2025-12-16 笔记本电脑 8000

问题: 产品名称产品价格 只依赖于 产品ID (主键的一部分),与 订单ID 无关。这就是部分函数依赖

第三范式

第三范式:每一列都必须直接依赖 于主键而不能间接依赖主键,即不能存在依赖传递。换句话说,任何非主键属性不能依赖于其他非主键属性。

具体含义:

  • 消除传递函数依赖 。即如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> A → B A \rightarrow B </math>A→B 且 <math xmlns="http://www.w3.org/1998/Math/MathML"> B → C B \rightarrow C </math>B→C(其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> A A </math>A 是主键),那么 <math xmlns="http://www.w3.org/1998/Math/MathML"> C C </math>C 对 <math xmlns="http://www.w3.org/1998/Math/MathML"> A A </math>A 就是传递依赖,需要消除

举个例子来解释第三范式:订单表里面有用户ID,就不应该还冗余一列用户的注册时间。因为用户的注册时间依赖于用户ID(在订单表里面是非主键,在用户表里面是主键),所以用户的注册时间如果存在于订单表,就是有间接依赖,即依赖传递的情况。

再举个违反 3NF 的例子: (假设 客户ID 是主键)

客户ID 客户姓名 所在城市 城市邮编
C001 张三 上海 200000
C002 李四 上海 200000
C003 王五 北京 100000

问题: 城市邮编 依赖于 所在城市,而 所在城市 依赖于 客户ID。 <math xmlns="http://www.w3.org/1998/Math/MathML"> 客户 I D → 所在城市 → 城市邮编 客户ID \rightarrow 所在城市 \rightarrow 城市邮编 </math>客户ID→所在城市→城市邮编 这就是传递函数依赖 。每次有新的上海客户加入,城市邮编 都会重复存储。

总结❤️

在实际的数据库设计中,通常会努力达到 3NF。然而,有时为了提高查询性能 ,可能会故意违反 3NF ,采用反范式设计。为业务做出的妥协是可以理解的

我推荐大家可以利用DDD领域驱动设计的维度去结合三大范式来进行库表设计,比如在订单领域,地址就可以是订单表的列,可以冗余。在用户领域,地址就应该是一个实体,可以进行增删改查和独立的业务意义,有完整的生命周期。简单来说,就是在用户领域,地址应该被设置成一个单独ID的表,而在订单领域,地址只是一个值对象(无生命周期,下单即固化,类似于快照)

什么该冗余,什么不该冗余,有了一个切合实际的思维框架,而不是依赖于"经验"之谈,对项目的可维护性会有很大帮助

相关推荐
it_czz17 分钟前
LangSmith vs LangFlow vs LangGraph Studio 可视化配置方案对比
后端
蓝色王者19 分钟前
springboot 2.6.13 整合flowable6.8.1
java·spring boot·后端
柠檬叶子C20 分钟前
PostgreSQL 忘记 postgres 密码怎么办?(已解决)
数据库·postgresql
864记忆1 小时前
Qt创建连接注意事项
数据库·qt·nginx
花哥码天下1 小时前
apifox登录后设置token到环境变量
java·后端
毕设十刻1 小时前
基于Vue的迅读网上书城22f4d(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
程序员小寒1 小时前
从一道前端面试题,谈 JS 对象存储特点和运算符执行顺序
开发语言·前端·javascript·面试
薛定谔的猫19822 小时前
Langchain(十二)LangGraph 实战入门:用流程图思维构建 LLM 工作流
数据库·microsoft
hashiqimiya2 小时前
springboot事务触发滚动与不滚蛋
java·spring boot·后端
坐吃山猪2 小时前
ChromaDB02-代码实战
数据库·向量数据库·chromadb