数据库设计与范式
问题
如何进行数据库设计?三大范式是什么?什么时候需要反范式?
答案
数据库设计流程
三大范式
| 范式 | 要求 | 解决的问题 |
|---|---|---|
| 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 / TIMESTAMP | TIMESTAMP 占用更小 |
| 布尔 | TINYINT(1) | MySQL 无原生 BOOLEAN |
| 枚举 | ENUM 或 TINYINT | TINYINT 更灵活 |
| UUID | CHAR(36) 或 BINARY(16) | BINARY 性能更好 |
Q3: 主键用自增 ID 还是 UUID?
答案:
| 维度 | 自增 ID | UUID |
|---|---|---|
| 存储 | 4/8 字节 | 16/36 字节 |
| 索引性能 | 顺序插入,性能好 | 随机插入,页分裂多 |
| 分布式 | 需要额外方案 | 天然全局唯一 |
| 安全性 | 可猜测 | 不可猜测 |
推荐:单库用自增 ID,分布式用雪花 ID(Snowflake)。
相关链接
- 数据建模实战 - 实际建模案例
- MySQL 基础与查询 - SQL 语法