Sunday面试指南

数据库三大范式是什么?为什么实际项目有时需要反范式设计?

下面是一段教学用的模拟面试。

🧑‍💻 面试官:三大范式怎么理解?

🙋‍♂️ 我:字段不可再分,不能部分依赖,不能传递依赖。

🧑‍💻 面试官:这几句话放进一张选课表,你能说明该拆什么吗?

🙋‍♂️ 我:要先看哪些字段由学生决定,哪些由课程决定。

🧑‍💻 面试官:那订单保存购买时的商品名称,是不是违反范式?商品改名,历史订单也应该跟着改吗?

范式先看「一个事实由谁决定」。表里出现重复文字,不代表它们表达的就是同一个需要同步的事实。

面试速答(60 秒版)

数据库范式帮助我们按字段之间的依赖关系组织表,减少重复保存同一事实带来的更新、插入和删除异常。

第一范式要求按所定义的值域,每个位置保存一个值,避免同一类数据无限扩展成重复字段。第二范式在此基础上,要求非主属性不能只依赖某个候选键的一部分。第三范式进一步检查不合适的传递依赖。

例如选课表的键是学生号加课程号,学生姓名只依赖学生号,课程名称只依赖课程号,就应该分别放进学生表和课程表。

实际项目可以为读性能增加冗余,但要说明权威来源、更新方式和允许滞后的时间。历史订单里的购买时名称,则可能是在保存历史事实,不应直接当成当前商品名称的重复副本。

范式:这条事实由谁决定?

知识点详解:从一张选课表开始,一次只修一个问题

先说明“依赖”,否则范式只是三句口诀

假设表中有学生号、学生姓名、课程号、课程名称和成绩。一个学生可以选多门课,一门课也可以有多个学生。

一条成绩记录由“学生号 + 课程号”确定。知道学生号,就知道该学生当前姓名;知道课程号,就知道这门课当前名称。

这就是这里的函数依赖:相同的决定字段,不能对应两个不同的被决定值。它来自数据规则,不是看字段摆在哪一列。

范式分析需要看候选键和依赖。候选键是能够唯一决定记录、并且已经不能删掉组成部分的字段集合,不只看你最后选作主键的那个名字。属于任意一个候选键的属性,叫作主属性;不属于任何候选键的,叫作非主属性。

第一范式:别用不断加列保存同一类重复项

如果学生表写成课程1、课程2、课程3,第四门课出现,就要再改表结构。空位很多,查询“谁选了某门课”也不自然。

可以把每次选课作为一行,再由学生号和课程号关联。每个字段保存符合其值域的一个值。

这里的“不可再分”不是说字符串永远不能拆字,也不是数据库支持 JSON 就违反规则。要看数据域与建模需求,不用机械拆出“姓”和“名”来证明懂第一范式。

第二范式:这些字段需要整个键才能决定吗?

回到选课表。成绩需要学生号与课程号共同决定,但学生姓名只需要学生号,课程名称只需要课程号。

这两个字段只依赖组合键的一部分。每次选课都重复存姓名和课名,改名时就要改很多行。

于是拆成学生表、课程表和选课表。选课表保存学生号、课程号与成绩,名字放到各自负责的表里。

更准确地说,第二范式检查非主属性对候选键的部分依赖;不是换成一个自增主键,就能让原来的依赖问题自动消失。

第三范式:再检查绕了一步的依赖

假设学生表还有学院号和学院当前名称。学生号决定学院号,学院号又决定学院名称。

学院改名时,如果每个学生行都保存学院名称,仍要同步修改很多地方。因此可以建立学院表,学生表关联学院号。

这个简单例子符合常见的传递依赖解释。如果想说得更准确,可以参考 RPI 的数据库课程定义:对每个非平凡依赖 X → A,X 必须是超键,或者 A 是主属性。这里的超键,是能唯一决定记录的字段集合,不要求已经去掉多余字段。非平凡依赖则是说 A 不包含在 X 中,不是“学生号决定学生号”这样的自我重复。

所以,不能把所有情况只压成“非主键不能决定非主键”。前面的选课例子已经足够回答大多数基础追问;讨论严格定义时,再把候选键和主属性说清楚。

这些拆分不只是让改名更方便。假如学院名称只保存在学生行中,新学院还没有学生,就没地方记录它,这叫插入异常;最后一名学生的记录被删除,学院信息也跟着消失,则是删除异常。拆表是在让不同事实各有合适的保存位置。

这里讲前三种范式,不把 BCNF 错称成第四范式。

部分依赖与传递依赖

这张图只列出用于解释依赖的字段。选课表仍然保留成绩等属于选课记录的字段,并不是拆表后把它们删掉了。

反范式与历史快照,为什么要分开?

假设高频列表需要展示当前学院名称,可以在某个读模型中冗余保存,减少查询联接。

这时要明确:学院表是权威来源;名称变化怎样同步;同步失败如何补偿;读模型允许落后多久。先测到读成本,再决定冗余,不因为 JOIN 两张表就立刻反范式。

再看订单商品名。如果字段语义是“购买时名称”,那么之后商品改名,它本来就不应该变化。这是另一个时间点的事实,不是当前名称的待同步副本。

所以,范式讨论之前先确定字段含义,往往比争论“重复字段到底好不好”更重要。

当前副本与历史快照要分开

面试官继续追问

单字段主键的表,天然满足第三范式吗?

不是。单字段键可以避免某些部分依赖,但仍可能有不合适的传递依赖,或者存在其他候选键。主键的形式不能代替完整分析。

拆表越多越好吗?

不是。拆表应该消除具体依赖和异常,同时保持必要关联与约束。无意义地拆散字段,会增加查询与维护成本。

怎么验证反范式值得做?

先比较真实查询的延迟和资源消耗,再测试更新、失败重试与重建。读快了,但名称永远同步不回来,不能算设计完成。

面试速记卡

  • 起点:先确定事实、函数依赖和候选键。
  • 第一范式:每个位置保存值域中的一个值,避免重复组建模。
  • 第二范式:检查非主属性对候选键的部分依赖。
  • 第三范式:继续检查依赖,避免不合适的传递保存。
  • 冗余:当前副本要同步;历史快照要保留当时事实。

公司面试真题

这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。

浏览公司面试真题 →
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历