Skip to content

[L1] 什么是数据库三范式?各范式分别解决了什么问题?

一句话结论

三范式逐级消除冗余:1NF 原子性、2NF 消除部分依赖、3NF 消除传递依赖。

体系讲解

三范式定义与各自解决的问题

范式核心要求解决的问题反例
1NF(第一范式)每列的值不可再分(原子性)消除"一格多值"导致的查询困难将手机号存为 "138,139" 放在同一列
2NF(第二范式)在满足 1NF 的基础上,非主键列必须完全依赖于主键(不允许部分依赖)消除联合主键下"只依赖部分主键"的冗余列订单详情表以 (order_id, product_id) 为主键,却存了 product_name(只依赖 product_id
3NF(第三范式)在满足 2NF 的基础上,非主键列之间不存在传递依赖消除"A→B→C"链式冗余订单表同时存 customer_idcustomer_city(city 依赖 customer_id,非直接依赖主键)

逐级递进关系

  • 满足 3NF → 必然满足 2NF → 必然满足 1NF
  • 每升一级,消除一种额外的数据冗余,更新异常随之减少

反范式:何时打破规则

严格遵守三范式会增加 JOIN 操作,在读多写少的场景下可能成为性能瓶颈。此时可以有意引入冗余字段(反范式),典型场景:

  • 订单表冗余存储 product_name,避免每次查询都 JOIN 商品表(商品名历史不可变)
  • 统计报表表提前聚合存储计数/金额,避免实时聚合大表
  • 用户动态表冗余存储 avatar_url,减少关联查询

反范式的代价是需要维护数据一致性(更新商品名时冗余字段也要同步),适合历史快照型或只增不改的数据

考察意图

  • 确认候选人能准确说出三范式各自的定义,而非混淆 2NF/3NF
  • 考察是否理解范式是手段而非目的——知道何时应该打破范式
  • 区分"背定义"和"能落地":能否举出项目中遇到的反范式场景

追问链

  1. 2NF 和 3NF 的区别是什么?用一句话区分。

    简答:2NF 消除的是对主键的部分依赖(针对联合主键),3NF 消除的是非主键列之间的传递依赖。2NF 管"非主键列与主键的关系",3NF 管"非主键列之间的关系"。

  2. 如果一张表只有单列主键,还需要考虑 2NF 吗?

    简答:不需要。2NF 只在联合主键(多列组合做主键)时才有意义——单列主键不存在"部分依赖"的情况,表如果满足 1NF 则自动满足 2NF。

  3. 订单表中同时存 customer_idcustomer_city,违反了哪个范式?应如何修复?

    简答:违反 3NF(customer_city 通过 customer_id 传递依赖主键,非直接依赖)。修复:将 customer_city 移入 customers 表,订单表只保留 customer_id,需要城市时 JOIN 查询。

  4. 什么情况下可以不遵守三范式(反范式)?有什么代价?

    简答:读多写少、历史快照(订单快照商品名)、大表频繁 JOIN 影响性能时可引入冗余字段。代价是写入时需同步维护冗余列,若更新不一致会产生数据不一致风险,适合"写入后基本不改"的字段。

易错点

  1. 混淆 2NF 与 3NF:2NF 针对"非主键列与主键的部分依赖",3NF 针对"非主键列之间的传递依赖"。面试中将两者说反或混用是最常见错误。

  2. 认为三范式是银弹:实际项目中订单表、日志表、报表等常见场景几乎都有意违反三范式(反范式设计)。只答"遵守三范式"而不提反范式,说明缺乏工程经验。

  3. 以为单列主键表不需要了解 2NF:2NF 确实仅在联合主键时才有"部分依赖"的问题,但面试中考察的是能否说清"2NF 防止的是哪类冗余"。不理解 2NF 语义,无法判断哪些表设计是有意反范式、哪些是设计失误。

代码示例

sql
-- ❌ 违反 2NF:product_name 只依赖 product_id,不依赖整个联合主键
CREATE TABLE order_items_bad (
    order_id     INT,
    product_id   INT,
    product_name VARCHAR(100),  -- 冗余:只依赖 product_id
    quantity     INT,
    PRIMARY KEY (order_id, product_id)
);

-- ✅ 符合 2NF:product_name 移入 products 表,order_items 只存关系数据
CREATE TABLE order_items (
    order_id   INT,
    product_id INT,
    quantity   INT,
    PRIMARY KEY (order_id, product_id)
);

基于 Apache License 2.0 开源