字节面试:破坏数据库三范式换高性能,你会吗?

87 阅读5分钟

沉默是金,总会发光

大家好,我是沉默

我们在设计数据库时,常常被一句话刷屏:“一定要遵守三大范式!”

但现实中,许多优秀的系统反而“破坏”了范式。难道三大范式是“看起来很美”,实际却不管用?

今天这篇文章,就来帮你通透理解:

✅ 三大范式到底是什么?
✅ 哪些场景你可以放心“破坏范式”?
✅ 为什么说范式不是圣旨?

**-**01-

三大范式到底是什么?

数据库设计的三大范式,类比一下,就像整理房间的三步曲:

  • 第一范式(1NF):东西不能堆一起,要“每件物品各就各位”

  • 第二范式(2NF):同类物品要放一起,不要一个柜子塞两类东西

  • 第三范式(3NF):信息不重复,一个标签贴在一个地方

数据库三大范式就是为了消除冗余、提高一致性、简化维护,它不是约束你,而是帮助你。

接下来我们一个一个拆解。

1. 第一范式(1NF):字段必须原子化,不可再分

举例:有这样一个学生表

| 学生ID

|

姓名

|

电话号码

| | --- | --- | --- | |

1

|

张三

|

123456789, 987654321

| |

2

|

李四

|

555555555

|

问题:电话有多个,逗号分隔,这就像你把袜子、内裤、T恤全塞进一个抽屉——乱!

正确做法:拆成两张表!

  • 学生表

| 学生ID

|

姓名

| | --- | --- | |

1

|

张三

| |

2

|

李四

|

  • 电话表

| 电话ID

|

学生ID

|

电话号码

| | --- | --- | --- | |

1

|

1

|

123456789

| |

2

|

1

|

987654321

| |

3

|

2

|

555555555

|

这样一来,每一列都是原子值,查询、更新、统计都更轻松。

2. 第二范式(2NF):字段必须完全依赖主键,不能只依赖“其中一半”

这是在 1NF 基础上的升级,主要应用在 复合主键 的表。

来看这个订单详情表

| 订单ID

|

商品ID

|

商品名称

|

数量

|

单价

| | --- | --- | --- | --- | --- | |

1001

|

A01

|

苹果

|

10

|

2.5

| |

1001

|

A02

|

橙子

|

5

|

3.0

| |

1002

|

A01

|

苹果

|

7

|

2.5

|

问题:商品名称和单价,其实只跟商品ID有关,和订单ID没关系。这就像你把商品简介贴在订单单据上——重复冗余。

正确拆法:

  • 订单详情表

| 订单ID

|

商品ID

|

数量

| | --- | --- | --- | |

1001

|

A01

|

10

| |

1001

|

A02

|

5

| |

1002

|

A01

|

7

|

  • 商品表

| 商品ID

|

商品名称

|

单价

| | --- | --- | --- | |

A01

|

苹果

|

2.5

| |

A02

|

橙子

|

3.0

|

从此,商品价格改了只需动一处,不怕多处更新错漏。

3. 第三范式(3NF):字段不能通过“其他字段”间接依赖主键

还是那句话——一个字段的值,必须直接依赖主键,而不是依赖另一个非主键字段。

举个例子

| 员工ID

|

员工姓名

|

部门ID

|

部门名称

| | --- | --- | --- | --- | |

E01

|

王五

|

D01

|

销售部

| |

E02

|

赵六

|

D02

|

技术部

| |

E03

|

孙七

|

D01

|

销售部

|

问题:部门名称其实依赖的是部门ID,而部门ID又依赖员工ID,形成了“传递依赖”。

正确分拆:

  • 员工表

| 员工ID

|

员工姓名

|

部门ID

| | --- | --- | --- | |

E01

|

王五

|

D01

| |

E02

|

赵六

|

D02

| |

E03

|

孙七

|

D01

|

  • 部门表

| 部门ID

|

部门名称

| | --- | --- | |

D01

|

销售部

| |

D02

|

技术部

|

一改部门名称,全体员工自动生效,省心!

**-**02-

哪些场景你可以放心“破坏范式”?

你可能会问:

三大范式这么香,为什么真实项目反而会反范式设计?

因为设计数据库不只是追求纯洁性,还要考虑性能、复杂度、成本与业务演进速度。

以下是“可以破坏范式”的 7 类典型场景:

1. 性能优化:冗余数据,减少 JOIN

订单表原本只保存用户ID,要查用户信息得联表。

优化后:

在订单表冗余用户名、地址,直接展示,查询更快。

2. 简化开发:少联表,逻辑简单

CMS 系统中,文章表 + 分类表经常联表取分类名。

优化后:

直接在文章表冗余“分类名称”,接口简单、开发轻松。

3. 报表系统:星型模型天然反范式

BI 报表中,事实表直接存储产品名、类别名、日期等维度数据。

目的: 快速生成图表,避免复杂 SQL。

4. 实时业务需求:保持写时一致,提高查询效率

例如账户余额、好友数量,不实时计算,而是写入时维护一个冗余字段。

5. 快速迭代:初创项目更看重上线速度

刚起步的项目不需要复杂拆表,字段全堆一个表中,迭代快。

后期需求稳定,再逐步范式化。

6. 降低协作成本:结构直观,新人易懂

冗余字段降低了逻辑复杂度,前后端都更容易读懂和维护。

7. 读多写少场景:读性能优先,牺牲部分写入一致性

如用户主页展示的统计数据、推荐内容等。

**-**03-

为什么说范式不是圣旨?

设计数据库,不是靠死记范式,而是基于业务权衡

推荐策略:

| 场景

|

是否严格范式

|

原因

| | --- | --- | --- | |

核心交易系统

|

✅ 是

|

强一致性、高并发

| |

报表分析系统

|

❌ 否

|

查询性能优先

| |

初创快速迭代

|

❌ 否

|

上线速度 > 完美设计

| |

成熟大型系统

|

⚖️ 权衡

|

范式 + 局部冗余混合

|

而是否“破坏”范式?请始终牢记这句话:

设计是为了更好地服务业务,不是为了奉命行事。

**-**04-

总结

  • 1NF:每列只存一个值,别搞“电话1,电话2”

  • 2NF:字段不能只依赖主键的一部分

  • 3NF:字段不能间接依赖主键,要直接挂钩

一句话总结:

范式是一种“默认的正确”,但不是唯一正确。设计数据库,是在性能、扩展性与开发效率之间找到最优解。

**-**05-

粉丝福利

点点关注,送你 SQL Server 性能调优实战,如果你正在优化 SQL,又或者刚准备调优性能。可以仔细阅读一下,或许对你有所帮助!