news 2026/5/23 23:28:18

MySQL 三大范式与反范式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL 三大范式与反范式

我刚工作的时候,设计了一张用户表,把所有信息都塞进去了:用户 ID、姓名、年龄、邮箱、地址、订单 ID、订单金额、订单状态……结果表有 50 多个字段,查询慢得要命,还经常冗余数据不一致。

后来 DBA 帮我重新设计表结构,遵循了数据库三大范式,性能直接提升了 10 倍。

今天咱们就来聊聊 MySQL 的三大范式与反范式,看完这篇,你就能设计出高性能的表结构了。

为什么要有范式?

范式(Normal Form,NF)是数据库设计的一套规范,目的是:

  1. 减少数据冗余:同样的数据不存多份
    1. 避免数据异常:插入异常、删除异常、更新异常
    1. 提高数据一致性:数据只存一份,不会出现不一致
      但是,过度遵循范式会导致表太多、JOIN 太多,性能反而下降。所以有时候要反范式(故意冗余数据)。

第一范式(1NF):列不可再分

定义:表中的每一列都是原子性的(不可再分)。

违反 1NF 的例子

-- 违反 1NF:phone 列存了多个值CREATETABLEusers(idINTPRIMARYKEY,nameVARCHAR(50),phoneVARCHAR(100)-- 存了 "13800138000,13900139000");```**问题**:如果要查 "13800138000" 这个手机号的用户,很难写 SQL。 ### 符合 1NF 的设计 ```sql-- 符合 1NF:拆成多行CREATETABLEusers(idINTPRIMARYKEY,nameVARCHAR(50));CREATETABLEuser_phones(user_idINT,phoneVARCHAR(20),PRIMARYKEY(user_id,phone));```**或者**(如果只需要 2 个手机号): ```sqlCREATETABLEusers(idINTPRIMARYKEY,nameVARCHAR(50),phone1VARCHAR(20),phone2VARCHAR(20));```## 第二范式(2NF):消除部分依赖 **定义**:表中的每一列都**完全依赖**于主键(不能是部分依赖)。 **前提**:表必须有**联合主键**(如果主键是单字段,自动满足 2NF)。 ### 违反 2NF 的例子 ```sql-- 违反 2NF:联合主键 (order_id, product_id),但 product_name 只依赖于 product_id(部分依赖)CREATETABLEorder_items(order_idINT,product_idINT,product_nameVARCHAR(50),-- 只依赖于 product_id,不依赖于 order_idquantityINT,PRIMARYKEY(order_id,product_id));```**问题**: 1. **数据冗余**:同一个`product_id``product_name`存了多次 2. 2. **更新异常**:如果`product_name`改了,要更新多行 3. 3. **插入异常**:如果还没订单,就没法插产品信息 ### 符合 2NF 的设计 ```sql-- 拆成两张表CREATETABLEorder_items(order_idINT,product_idINT,quantityINT,PRIMARYKEY(order_id,product_id));CREATETABLEproducts(product_idINTPRIMARYKEY,product_nameVARCHAR(50));```## 第三范式(3NF):消除传递依赖 **定义**:表中的每一列都**直接依赖**于主键(不能是传递依赖)。 ### 违反 3NF 的例子 ```sql-- 违反 3NF:department_name 依赖于 department_id,department_id 依赖于 emp_id(传递依赖)CREATETABLEemployees(emp_idINTPRIMARYKEY,emp_nameVARCHAR(50),department_idINT,department_nameVARCHAR(50)-- 传递依赖:emp_id → department_id → department_name);```**问题**: 1. **数据冗余**:同一个部门的员工,部门名存了多次 2. 2. **更新异常**:如果部门名改了,要更新多行 3. 3. **插入异常**:如果还没员工,就没法插部门信息 ### 符合 3NF 的设计 ```sql-- 拆成两张表CREATETABLEemployees(emp_idINTPRIMARYKEY,emp_nameVARCHAR(50),department_idINT);CREATETABLEdepartments(department_idINTPRIMARYKEY,department_nameVARCHAR(50));```## BC 范式(BCNF):消除主属性对候选键的部分依赖 **定义**:如果表有**多个候选键**,不能有主属性对候选键的部分依赖。 ### 违反 BCNF 的例子 ```sql-- 假设:(student_id, course_id) 是联合主键,(professor_id, course_id) 也是候选键CREATETABLEenrollments(student_idINT,course_idINT,professor_idINT,PRIMARYKEY(student_id,course_id));```**问题**:`professor_id`依赖于`course_id`(部分依赖候选键),导致数据冗余。 ### 符合 BCNF 的设计 ```sql-- 拆成两张表CREATETABLEenrollments(student_idINT,course_idINT,PRIMARYKEY(student_id,course_id));CREATETABLEcourses(course_idINTPRIMARYKEY,professor_idINT);```## 第四范式(4NF)和第五范式(5NF) 这两范式很少用到,简单提一下: - **4NF**:消除多值依赖(比如一个表同时存了"课程"和"爱好"两个多值属性) - - **5NF**:消除连接依赖(确保表是不可再分的) **实际开发中,一般遵循到 3NF 就够了。** ## 反范式(Denormalization):故意冗余数据 **反范式**指的是:**故意违反范式,冗余一些数据**,目的是**减少 JOIN,提升查询性能**。 ### 什么时候要反范式? 1. **读多写少**的场景(比如电商的订单表、商品表) 2. 2. **JOIN 太多**,导致查询性能差 3. 3. **实时性要求高**,不能每次都 JOIN ### 反范式的例子 #### 例子 1:订单表冗余用户名 **遵循 3NF 的设计**: ```sqlCREATETABLEorders(order_idINTPRIMARYKEY,user_idINT,amountDECIMAL(10,2),created_atDATETIME);CREATETABLEusers(user_idINTPRIMARYKEY,usernameVARCHAR(50));-- 查询订单时,要 JOIN users 表SELECTo.order_id,u.username,o.amountFROMorders oJOINusers uONo.user_id=u.user_id;

反范式设计(冗余username):

CREATETABLEorders(order_idINTPRIMARYKEY,user_idINT,usernameVARCHAR(50),-- 冗余字段amountDECIMAL(10,2),created_atDATETIME);-- 查询订单时,不需要 JOINSELECTorder_id,username,amountFROMorders;

好处:查询快了(不需要 JOIN)。

代价

  1. 如果用户改了用户名,要更新所有相关订单(可以用触发器或者定时任务)
    1. 冗余字段占存储空间
例子 2:商品表冗余分类名

遵循 3NF 的设计

CREATETABLEproducts(product_idINTPRIMARYKEY,product_nameVARCHAR(50),category_idINT);CREATETABLEcategories(category_idINTPRIMARYKEY,category_nameVARCHAR(50));-- 查询商品时,要 JOIN categories 表SELECTp.product_name,c.category_nameFROMproducts pJOINcategories cONp.category_id=c.category_id;

反范式设计(冗余category_name):

CREATETABLEproducts(product_idINTPRIMARYKEY,product_nameVARCHAR(50),category_idINT,category_nameVARCHAR(50)-- 冗余字段);-- 查询商品时,不需要 JOINSELECTproduct_name,category_nameFROMproducts;

实战建议

1. 先遵循 3NF,再按需反范式

建议流程

  1. 先按 3NF 设计表结构(减少冗余,保证一致性)
    1. 上线后,如果发现某些查询性能差(JOIN 太多),再考虑反范式(冗余字段)

2. 反范式时,要考虑数据同步问题

如果冗余字段会更新,要设计好同步机制:

  • 方案 1:用触发器(自动同步)
  • – 当 users 表的 username 更新时,自动更新 orders 表的 username
  • DELIMITER $$
  • CREATE TRIGGER update_username AFTER UPDATE ON users
  • FOR EACH ROW
  • BEGIN
  • UPDATE orders SET username = NEW.username WHERE user_id = NEW.user_id;
  • END$$
  • DELIMITER ;
  • 方案 2:用定时任务(定期同步)
  • – 每天凌晨同步用户名
  • UPDATE orders o
  • JOIN users u ON o.user_id = u.user_id
  • SET o.username = u.username
  • WHERE o.username != u.username;
  • 方案 3不更新(允许短期不一致,比如订单的用户名,下完单就不应该改了)

3. 用「宽表」做报表查询

宽表指的是:把多张表的数据冗余到一张表,专门用来做报表查询(不需要 JOIN)。

-- 宽表:订单明细表(冗余了用户信息、商品信息、分类信息)CREATETABLEorder_details(order_idINT,user_idINT,usernameVARCHAR(50),product_idINT,product_nameVARCHAR(50),category_nameVARCHAR(50),amountDECIMAL(10,2),created_atDATETIME);```**好处**:报表查询超快(不需要JOIN)。**代价**:数据冗余,同步复杂。## 总结-**第一范式(1NF)**:列不可再分(原子性)--**第二范式(2NF)**:消除部分依赖(联合主键时,每一列都完全依赖于主键)--**第三范式(3NF)**:消除传递依赖(每一列都直接依赖于主键)--**BC 范式(BCNF)**:消除主属性对候选键的部分依赖--**反范式**:故意冗余数据,减少JOIN,提升查询性能--实战建议:先遵循3NF,再按需反范式;反范式时,要考虑数据同步问题;用「宽表」做报表查询 如果你能把三大范式和反范式讲清楚,并且能设计合理的表结构,面试官绝对觉得你是高级开发。---**实战代码都在我本地跑过,你可以放心复制。**如果有问题,欢迎评论区交流!
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 23:22:27

Dell服务器数据恢复:RAID拓扑识别与无损镜像实战指南

1. 这不是“软件点几下就能搞定”的事:Dell服务器数据恢复的本质认知很多人第一次面对Dell服务器硬盘灯全灭、RAID状态变红、业务系统突然报“找不到卷”时,第一反应是去搜“dell 数据恢复 软件免费”,下载几个带“万能”“极速”字样的工具&…

作者头像 李华
网站建设 2026/5/23 23:20:53

Mythos如何实现大模型在漏洞挖掘中的因果推理跃迁

1. 这不是一次普通升级:Mythos 的能力跃迁本质是什么?如果你过去三年持续关注大模型在安全领域的实际表现,看到 Anthropic 发布 Claude Mythos Preview 的第一反应不会是“又一个新模型”,而是“时间线被压缩了”。这不是渐进式优…

作者头像 李华
网站建设 2026/5/23 23:20:01

SSH安全加固:禁用弱加密算法的实操指南

1. 为什么禁用弱加密算法不是“可选项”,而是SSH上线前的必过门槛我第一次在客户现场接手一台刚部署的CentOS 7跳板机时,安全扫描报告里赫然标红了三条:ssh-rsa签名算法被标记为CRITICAL,diffie-hellman-group1-sha1密钥交换被判定…

作者头像 李华
网站建设 2026/5/23 23:16:19

WzComparerR2完整指南:冒险岛游戏数据提取与可视化分析工具

WzComparerR2完整指南:冒险岛游戏数据提取与可视化分析工具 【免费下载链接】WzComparerR2 Maplestory online Extractor 项目地址: https://gitcode.com/gh_mirrors/wz/WzComparerR2 WzComparerR2是一款功能强大的冒险岛游戏数据提取与分析工具,…

作者头像 李华
网站建设 2026/5/23 23:09:14

JMeter分布式压测实战:从单机瓶颈到三节点集群搭建

1. 为什么单台JMeter跑不动,却总有人硬扛?——分布式压测不是“锦上添花”,而是“生死线”你有没有遇到过这样的场景:用JMeter本地跑500个线程,CPU刚到60%,内存还剩3G,一切看起来很稳&#xff1…

作者头像 李华
网站建设 2026/5/23 23:03:39

机器学习工程师实战书单:从跑通代码到源码级调试

1. 这份书单不是“随便搜来的”,而是我筛掉27本、重读11本、在3个真实项目里反复验证后整理的你点开这个标题,大概率正站在机器学习学习的十字路口:想系统入门却怕踩坑,想深入实践又不知从哪本开始啃,或者已经学了一阵…

作者头像 李华