MySQL范式和反范式
范式
是用一组规则定义的数据库设计标准,旨在确保数据库结构合理,避免数据冗余和异常。
目的
- 消除数据的重复,提高存储效率
- 防止数据异常(插入、删除、更新异常)
- 提高数据的完整性和一致性
第一范式
- 定义
- 所有列(字段)必须是原子性的(不可拆分的基本数据单位)
- 表中的每个字段都应包含原子值,不能存储集合、数组等多值数据
- 要求
- 每个字段只存储单一值,无重复列
- 每一行是唯一的(通常用主键)
举例:
- 不符合1NF:一列存多个电话号码,用逗号分隔
- 比如: 一个“电话号码”字段存着“13812345678,13987654321”两个号码(多个值在一列)
- 符合1NF:用多个字段存放各个电话号码,或用一行存一个电话
第二范式
- 定义
- 在1NF基础上,消除表中的部分依赖(部分依赖是指非主键列依赖于主键的一部分)
- 要求
- 所有非主键列必须完全依赖于整个主键(对于复合主键)
举例:
- 表:订单明细(order_id, product_id, product_name)
- 关键点:
- 如果主键是(order_id, product_id)
- product_name 依赖product_id,不是整个主键 → 不符合2NF
- 解决方案:将product_name单独放到产品表
第三范式
- 定义
- 在2NF基础上,消除非主键列之间的传递依赖
- 要求
- 所有非主键列都必须直接依赖于主键,不依赖于其他非主键列
举例:
- 表:员工(员工ID,部门ID,部门名称)
- 依赖关系:
- 部门名称依赖于部门ID
- 部门ID依赖于员工ID
- 由于部门名称依赖于部门ID(非主键),这是传递依赖,不符合3NF
- 解决方案:将部门信息单独拆分到新表
- 依赖关系:
更高阶的范式
- BCNF(Boyce-Codd Normal Form):比3NF更严格,要求每个决定因素都是超键
- 4NF、5NF:更复杂,涉及多值依赖和连接依赖,较少在日常开发中应用
如何进行判断依赖和主键
判断依赖
-
- 依赖关系的基本概念
- 依赖:某个字段(或多个字段)值的确定依赖于另一个字段(或多个字段)
- 简述:
- 如果字段B的值都是由字段A的值唯一决定的,称为“B依赖A”
- 记作:A → B
-
- 如何识别依赖关系?
- 分析业务逻辑:理解数据的实际关系
- 观察数据的函数关系:
- 比如:
- 一个“订单ID”决定了“订单日期” → 订单ID → 订单日期
- 某个“学生ID”决定“学生姓名” → 学生ID → 姓名
- 借助示意图:
- 可以画出依赖关系图(如:箭头指向依赖方)
订单ID | 订单日期 | 客户ID | 客户名 |
---|
- 依赖关系:
- 订单ID → 订单日期
- 客户ID → 客户名
- 推断:
- 订单ID决定订单日期
- 客户ID决定客户名
判断主键
- 观察每一列(字段):
- 哪个字段的值在整个表中是唯一的?(比如:身份证号、学号、订单编号)
- 组合判断:
- 如果单个字段不能唯一标识一条记录,就考虑多个字段结合
- 比如:
- “订单ID”唯一 → 订单ID就是主键
- 但:
- “订单ID + 商品ID”,这个组合也能唯一标识一条订单中的商品项
- 实际操作:
- 查看每个字段的值,识别唯一性
- 使用数据库设计工具或SQL语句:
select count(distinct 某字段) from 表名;
- 如果结果等于总记录数,说明该字段唯一。
- 当单一字段不能唯一标识行时,用多个字段组合作为主键
- 如:
- 学生-课程关联表:学生ID + 课程ID
- 判断依据:联合唯一性
总结
事项 | 方法 | 举例 |
---|---|---|
依赖关系 | 分析业务逻辑或观察数据中的“唯一决定关系” | 订单ID → 订单日期 |
确定主键 | 查看哪些字段的值在表中唯一 | 订单ID是唯一则单字段主键 |
组合主键 | 多个字段合成唯一标识 | 学生ID + 课程ID组成主键 |
示例
学生ID | 课程ID | 课程名 | 教师名 |
---|---|---|---|
1 | 101 | 数学 | 老师A |
2 | 102 | 语文 | 老师B |
1 | 102 | 语文 | 老师B |
-
满足1NF:每个字段都是单一值
-
是否满足2NF?
- 主键可能是(学生ID + 课程ID)
- 课程名和教师名只依赖课程ID,不是整个复合主键 → 不满足2NF。
- 解决:拆两张表——
- 学生选课表:学生ID,课程ID
- 课程表:课程ID,课程名,教师名
-
是否满足3NF?
- 课程表只有课程相关信息,没有传递依赖。
示例2:
反范式
:为了提高查询速度或简化复杂查询,有意将部分冗余数据引入设计,放宽或取消范式规范。
目的
- 目标:
- 减少多表连接(JOIN)操作
- 提升读取性能,尤其在大数据量和高并发场景
- 简化应用层的数据访问逻辑
何时采用反范式
- 查询频繁涉及复杂多表JOIN,影响性能
- 读操作远多于写操作(写入时维护冗余数据带来额外成本)
- 实时性要求高,不能接受延迟
- 业务场景对数据一致性要求相对较低(允许一定的冗余和同步)
例子:
- 不反范式(正常化设计):
- 查询订单和订单详情需要多表JOIN
- 代码中拼接复杂SQL,逻辑繁琐
- 采用反范式:
- 将订单详情部分数据直接存储在订单表里
- 查询只需访问一张表,逻辑简单,代码清晰
常见方式
- 将关联的表合并成一张表,避免JOIN操作
- 在主表中添加冗余字段,如冗余姓名、类别等
- 复制部分数据到不同的表中,方便快速查询
风险与管理
- 数据不一致:冗余数据不同步可能导致数据差异
- 数据不一致主要是因为冗余数据在多个位置存储后,没有保持同步,导致各个副本或字段中的信息出现差异。
为什么会导致数据不一致?
-
- 缺少同步机制
- 在反范式设计中,冗余数据通常存储在多个表或位置。
- 如果没有有效的同步策略(如触发器、业务逻辑或应用程序控制同步),在数据变更时,某些冗余字段没有及时更新。
-
- 写操作不完整或遗漏
- 更新、删除、插入操作中,某些地方的冗余数据没有一起更新。
- 例如:更新订单状态时,只更改订单表中的状态字段,但未同步更新订单的详细视图或历史记录。
-
- 并发修改造成的冲突
- 多个事务同时修改不同副本,可能导致不同步或出现“最后写入”覆盖的问题。
-
- 缺少约束或触发器
- 没有设置约束(如触发器、外键关系)来保证冗余字段在数据变更时自动同步。
-
- 业务逻辑缺陷
- 业务程序未考虑数据同步问题,导致不同的数据源或存储之间数据不同。
-
维护成本增加:更新时需要同步多份数据
-
设计复杂度:需要额外的代码逻辑保证数据一致性
因此,反范式应谨慎使用,只在确有性能瓶颈或特殊需求时采用
实际应用
- 应用建议:
- 在设计数据库时,遵循至少3NF,可以最大程度降低数据冗余和异常
- 但也会带来更多的表,查询可能变复杂,性能可能降低
- 常用过程中会在范式和性能之间做权衡(有时会适当范式反范,即反范式化)
总结
范式 | 目的 | 核心原则 | 典型表现 |
---|---|---|---|
1NF | 原子性 | 每个字段只存基本值 | 没有多值字段 |
2NF | 消除部分依赖 | 非主键都依赖整个主键 | 对复合主键特别重要 |
3NF | 消除传递依赖 | 非主键不依赖于其他非主键 | 避免冗余存储冗余数据 |
反范式 | 动机 | 典型做法 | 潜在问题 |
---|---|---|---|
反范式 | 提升性能,简化查询 | 添加冗余字段、合并表 | 可能导致数据不一致 |
-
范式是符合某一种级别的关系模式的集合。构造数据库必须遵循一定的规则。在关系数据库中,这种规则就是范式
- 范式优点:减少了数据冗余,数据表更新操作快、占用存储空间少
- 范式缺点:查询时通常需要多表关联查询,更难进行索引优化
-
反范式的过程就是通过冗余数据来提高查询性能,但冗余数据会牺牲数据一致性
- 反范式优点:所有的数据都在同一张表中,可以减少表关联,更好进行索引优化
- 反范式缺点:存在大量冗余数据,数据维护成本更高