Yihui’s Blog

数据库三大范式是什么?实际项目中如何平衡范式化和反范式化?

日期:2026-09-27
标签:#面试 #MySQL #数据库
难度:中等
来源:牛面 MySQL 题库
答案说明:根据可访问的题目详情改写

一句话答案

1NF强调字段原子性,2NF消除对复合键的部分依赖,3NF消除非主属性间的传递依赖;反范式化是有代价的性能取舍。

面试口语版(约 60 秒)

1NF 要求列值在当前模型中不可再分;2NF 在 1NF 基础上消除非键列对复合候选键的部分依赖;3NF 再消除非键列对键的传递依赖。

范式化减少重复和更新异常,适合交易主数据;反范式化是用额外存储和同步成本换读取便利,应以真实查询负载决定。

原理拆解与场景

例如订单明细 (order_id, product_id, product_name, qty) 的键是 (order_id, product_id),而 product_name 只依赖 product_id,违反 2NF;拆为产品表并保留 product_id 引用。若订单必须展示下单时的商品名称,product_name_snapshot 是明确业务快照,并非无理由重复。

3NF 例子:员工表同时存 dept_id 与可由部门确定的 dept_name,名称变更会造成多行更新;可拆部门表。若读链路频繁 JOIN,可加受控冗余、物化汇总或缓存,并定义更新事件、重试、对账。

关键边界与工程取舍

“原子”由业务语义决定,地址是否拆省市区要看查询要求。候选键不止主键;2NF 的部分依赖问题只在存在复合候选键时才典型。冗余字段不能只加不管,需定义写入权威源和一致性容忍时间。

面试官递进追问

1. 如何举例说明部分依赖和传递依赖?

参考答案: 部分依赖是非主属性只依赖复合候选键的一部分。例如订单明细以 (order_id, product_id) 为键,但当前 product_name 仅由 product_id 决定,应拆到产品表。传递依赖可用员工表说明:employee_id → dept_id → dept_name,部门名经由部门 ID 依赖员工键,宜拆部门表。判断依据是业务中的函数依赖和全部候选键,不能仅凭“表里加了一个自增主键”就认定已经满足所有范式要求。

2. 订单商品名称为什么可能要做快照?

参考答案: 因为订单需要回答“成交当时买了什么”,商品主表回答的是“现在叫什么”。若只实时关联商品名称,商家改名后历史订单、退款凭证和客服记录都会变化。因此下单时保存名称、规格、成交价等业务快照,并保留商品 ID 供追溯;快照通常不随主数据更新。它记录了不同时间语义的事实,不应作为普通缓存参与同步修复。哪些字段冻结、哪些展示最新值,要在业务模型中明确。

3. 冗余字段异步更新失败时怎样发现并修复?

参考答案: 先定义权威字段、冗余字段和允许滞后的时间,再把业务修改与待发送事件放进同一个本地事务,避免“数据库成功但消息没有发出”。消费者按事件 ID 幂等处理,用业务版本防止旧消息覆盖新值,并记录重试次数、失败原因及死信。监控事件积压、最老未处理时间和版本落后量;定期按主键范围对账,发现差异后从权威源重建冗余字段。对账和修复也要带版本校验,避免覆盖并发新写;还应处理删除事件。历史快照则按其冻结语义检查,不能拿当前主数据强行覆盖。

自测

  • 合上笔记,用 60 秒复述一句话结论、一个例子和一个边界。
  • 完成第 3 个追问,写出你会核对的 SQL、指标或故障证据。

延伸阅读

参考资料

以 MySQL 8.4 为版本基准;官方资料核对日期:2026-09-27。

维护与整理 · Yihui在 GitHub 上编辑

继续阅读

浏览全部文章

SQL与NoSQL有什么区别?MySQL和MongoDB如何选型?实际项目中如何选择?

日期:2026-09-27 标签:#面试 #MySQL #数据库 难度:简单 来源:牛面 MySQL 题库 答案说明:独立整理(站内题目标记为 VIP,未读取会员答案) 一句话答案 MySQL与MongoDB的主要差异在数据模型、事务边界、查询方式和模式演化;按业务访问模式和一致性需求选型。 面试口语版(约 60…

阅读全文