跳到主要内容

数据库设计与范式

问题

如何进行数据库设计?三大范式是什么?什么时候需要反范式?

答案

数据库设计流程

三大范式

范式要求解决的问题
1NF每列不可再分(原子性)消除重复组
2NF满足 1NF,非主键列完全依赖主键消除部分依赖
3NF满足 2NF,非主键列不传递依赖消除传递依赖
normalization.sql
-- ❌ 不满足 1NF(地址可再分)
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
address VARCHAR(500) -- 省/市/区/详细地址混在一起
);

-- ✅ 满足 1NF
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
province VARCHAR(50),
city VARCHAR(50),
district VARCHAR(50),
street VARCHAR(200)
);

-- ❌ 不满足 2NF(课程名只依赖 course_id,部分依赖)
CREATE TABLE scores (
student_id INT,
course_id INT,
course_name VARCHAR(100), -- 只依赖 course_id
score INT,
PRIMARY KEY (student_id, course_id)
);

-- ✅ 满足 2NF(拆分)
CREATE TABLE courses (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE scores (
student_id INT,
course_id INT,
score INT,
PRIMARY KEY (student_id, course_id)
);

反范式设计

何时反范式

查询性能比数据冗余更重要时,适当反范式。典型场景:高频查询需要 JOIN 多表,可以冗余字段避免 JOIN。

denormalization.sql
-- 范式化:订单查商品名要 JOIN
SELECT o.id, p.name, o.quantity
FROM order_items o
JOIN products p ON o.product_id = p.id;

-- 反范式化:冗余商品名(下单时快照)
CREATE TABLE order_items (
id INT PRIMARY KEY,
product_id INT,
product_name VARCHAR(200), -- 冗余字段(快照)
product_price DECIMAL(10,2), -- 冗余字段(快照)
quantity INT
);
-- 好处:1.查询不需要 JOIN 2.商品改名不影响历史订单

常见表设计模式

common-patterns.sql
-- 软删除
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL;

-- 乐观锁
ALTER TABLE products ADD COLUMN version INT DEFAULT 0;

-- 多态关联
CREATE TABLE comments (
id INT PRIMARY KEY,
content TEXT,
commentable_type ENUM('post', 'video', 'product'),
commentable_id INT,
INDEX (commentable_type, commentable_id)
);

-- 树形结构(路径枚举法)
CREATE TABLE categories (
id INT PRIMARY KEY,
name VARCHAR(100),
path VARCHAR(500), -- 如 '/1/3/7'
INDEX (path)
);

常见面试问题

Q1: 一对多、多对多怎么设计?

答案

  • 一对多:在"多"的一方加外键。如用户→订单,订单表加 user_id
  • 多对多:中间表。如学生↔课程,创建 student_courses(student_id, course_id) 关联表

Q2: 字段类型怎么选?

答案

数据推荐类型说明
自增主键INT / BIGINT大表用 BIGINT
金额DECIMAL(10,2)绝不用 FLOAT
时间DATETIME / TIMESTAMPTIMESTAMP 占用更小
布尔TINYINT(1)MySQL 无原生 BOOLEAN
枚举ENUMTINYINTTINYINT 更灵活
UUIDCHAR(36)BINARY(16)BINARY 性能更好

Q3: 主键用自增 ID 还是 UUID?

答案

维度自增 IDUUID
存储4/8 字节16/36 字节
索引性能顺序插入,性能好随机插入,页分裂多
分布式需要额外方案天然全局唯一
安全性可猜测不可猜测

推荐:单库用自增 ID,分布式用雪花 ID(Snowflake)。

相关链接