一、数据库设计规范

1.1 为什么需要数据库设计规范

设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库。这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。

不好的数据库设计会导致:

  • 数据冗余,浪费存储空间
  • 插入异常、删除异常、更新异常
  • 数据不一致,维护困难
  • 查询性能低下

好的数据库设计应该:

  • 减少数据冗余
  • 避免数据异常
  • 保证数据一致性
  • 兼顾查询性能

1.2 数据库设计步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────┐
│ 1. 需求分析 │
│ 收集业务需求,确定系统功能 │
├─────────────────────────────────────────────┤
│ 2. 概念结构设计(ER模型) │
│ 绘制ER图,确定实体、属性、关系 │
├─────────────────────────────────────────────┤
│ 3. 逻辑结构设计 │
│ 将ER模型转换为关系模式(表结构) │
├─────────────────────────────────────────────┤
│ 4. 物理结构设计 │
│ 确定存储引擎、索引、分区等 │
├─────────────────────────────────────────────┤
│ 5. 数据库实施 │
│ 创建数据库、表、约束、索引 │
├─────────────────────────────────────────────┤
│ 6. 数据库运行与维护 │
│ 监控、优化、备份、扩展 │
└─────────────────────────────────────────────┘

二、关系型数据库范式

关系数据库有六种范式:

范式 名称 核心要求
1NF 第一范式 列的原子性,每一列不可再分
2NF 第二范式 在1NF基础上,消除部分依赖
3NF 第三范式 在2NF基础上,消除传递依赖
BCNF 巴斯-科德范式 消除主属性对候选键的部分依赖
4NF 第四范式 消除多值依赖
5NF 第五范式 消除连接依赖

在实际项目中,一般遵循到 第三范式(3NF) 即可,特殊情况可适度反范式设计以提升查询性能。


三、第一范式(1NF)

3.1 定义

列的原子性:每一列都是不可再分的最小数据单元,每个字段只包含一种数据。

3.2 反例与正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❌ 不符合1NF的表(联系方式列可再分):
┌────┬──────┬─────────────────────────────┐
│ ID │ 姓名 │ 联系方式 │
├────┼──────┼─────────────────────────────┤
│ 1 │ 张三 │ 13800138000, zhangsan@qq.com│
│ 2 │ 李四 │ 13900139000, lisi@qq.com │
└────┴──────┴─────────────────────────────┘

✅ 符合1NF的表:
┌────┬──────┬─────────────┬─────────────────┐
│ ID │ 姓名 │ 手机号 │ 邮箱 │
├────┼──────┼─────────────┼─────────────────┤
│ 1 │ 张三 │ 13800138000 │ zhangsan@qq.com │
│ 2 │ 李四 │ 13900139000 │ lisi@qq.com │
└────┴──────┴─────────────┴─────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- ❌ 错误的表设计
CREATE TABLE tb_user_bad (
id INT PRIMARY KEY,
name VARCHAR(20),
contact VARCHAR(100) -- 包含手机和邮箱,违反1NF
);

-- ✅ 正确的表设计
CREATE TABLE tb_user (
id INT PRIMARY KEY,
name VARCHAR(20),
phone VARCHAR(20),
email VARCHAR(50)
);

3.3 常见违反1NF的场景

场景 反例 正例
多值属性 hobbies: '篮球,足球,游泳' 拆分为 hobbies 关联表
复合属性 address: '湖北省武汉市' province, city, district
JSON字符串 info: '{"age":25,"sex":"男"}' age, sex 单独字段

四、第二范式(2NF)

4.1 定义

1NF 的基础上,要求非主属性完全依赖于主键,消除部分依赖

部分依赖:非主属性只依赖于主键的一部分(针对联合主键)。

4.2 反例与正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
❌ 不符合2NF的表(联合主键:学号+课程号):
┌────────┬────────┬────────┬────────┬──────────┐
│ 学号 │ 课程号 │ 课程名 │ 成绩 │ 学生姓名 │
├────────┼────────┼────────┼────────┼──────────┤
│ 001 │ C01 │ 数学 │ 90 │ 张三 │
│ 001 │ C02 │ 英语 │ 85 │ 张三 │
│ 002 │ C01 │ 数学 │ 88 │ 李四 │
└────────┴────────┴────────┴────────┴──────────┘

问题分析:
- 课程名 只依赖于 课程号(部分依赖)
- 学生姓名 只依赖于 学号(部分依赖)
- 数据冗余:张三的姓名重复存储

✅ 拆分为三个表:

学生表:
┌────────┬──────────┐
│ 学号 │ 学生姓名 │
├────────┼──────────┤
│ 001 │ 张三 │
│ 002 │ 李四 │
└────────┴──────────┘

课程表:
┌────────┬────────┐
│ 课程号 │ 课程名 │
├────────┼────────┤
│ C01 │ 数学 │
│ C02 │ 英语 │
└────────┴────────┘

成绩表:
┌────────┬────────┬────────┐
│ 学号 │ 课程号 │ 成绩 │
├────────┼────────┼────────┤
│ 001 │ C01 │ 90 │
│ 001 │ C02 │ 85 │
│ 002 │ C01 │ 88 │
└────────┴────────┴────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- ✅ 正确的表设计
CREATE TABLE tb_student (
student_id VARCHAR(10) PRIMARY KEY,
name VARCHAR(20) NOT NULL
);

CREATE TABLE tb_course (
course_id VARCHAR(10) PRIMARY KEY,
course_name VARCHAR(50) NOT NULL
);

CREATE TABLE tb_score (
student_id VARCHAR(10),
course_id VARCHAR(10),
score DECIMAL(5,2),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES tb_student(student_id),
FOREIGN KEY (course_id) REFERENCES tb_course(course_id)
);

五、第三范式(3NF)

5.1 定义

2NF 的基础上,要求消除传递依赖:非主属性必须直接依赖于主键,不能通过其他非主属性传递依赖。

传递依赖:A → B → C,即 C 通过 B 间接依赖于 A。

5.2 反例与正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
❌ 不符合3NF的表:
┌────────┬────────┬──────────┬──────────┬──────────┐
│ 学号 │ 姓名 │ 班级编号 │ 班级名称 │ 班主任 │
├────────┼────────┼──────────┼──────────┼──────────┤
│ 001 │ 张三 │ B01 │ 一班 │ 王老师 │
│ 002 │ 李四 │ B01 │ 一班 │ 王老师 │
│ 003 │ 王五 │ B02 │ 二班 │ 李老师 │
└────────┴────────┴──────────┴──────────┴──────────┘

问题分析:
- 班级名称、班主任 依赖于 班级编号
- 班级编号 依赖于 学号
- 传递依赖:学号 → 班级编号 → 班级名称/班主任
- 数据冗余:一班信息重复存储

✅ 拆分为两个表:

学生表:
┌────────┬────────┬──────────┐
│ 学号 │ 姓名 │ 班级编号 │
├────────┼────────┼──────────┤
│ 001 │ 张三 │ B01 │
│ 002 │ 李四 │ B01 │
│ 003 │ 王五 │ B02 │
└────────┴────────┴──────────┘

班级表:
┌──────────┬──────────┬──────────┐
│ 班级编号 │ 班级名称 │ 班主任 │
├──────────┼──────────┼──────────┤
│ B01 │ 一班 │ 王老师 │
│ B02 │ 二班 │ 李老师 │
└──────────┴──────────┴──────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
-- ✅ 正确的表设计
CREATE TABLE tb_class (
class_id VARCHAR(10) PRIMARY KEY,
class_name VARCHAR(20) NOT NULL,
head_teacher VARCHAR(20)
);

CREATE TABLE tb_student (
student_id VARCHAR(10) PRIMARY KEY,
name VARCHAR(20) NOT NULL,
class_id VARCHAR(10),
FOREIGN KEY (class_id) REFERENCES tb_class(class_id)
);

5.3 三范式总结

范式 要求 解决的问题
1NF 列不可再分 多值属性、复合属性
2NF 消除部分依赖 联合主键导致的冗余
3NF 消除传递依赖 非主属性传递依赖导致的冗余
1
2
3
4
5
1NF ──→ 2NF ──→ 3NF
│ │ │
│ │ └── 消除传递依赖
│ └── 消除部分依赖
└── 列的原子性

六、反范式设计

6.1 什么时候需要反范式

在实际项目中,完全遵循范式可能导致查询性能下降(需要大量 JOIN 操作)。此时可适度反范式设计:

场景 反范式设计 原因
高频查询的关联字段 冗余存储关联字段 减少 JOIN 次数
统计数据 增加统计字段 避免实时计算
历史数据 快照字段 保留历史状态
1
2
3
4
5
6
7
8
9
10
11
-- 反范式示例:订单表冗余用户姓名
CREATE TABLE tb_order (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
user_name VARCHAR(20), -- 冗余字段:下单时的用户名
total_amount DECIMAL(10,2),
create_time DATETIME,
INDEX idx_user_id(user_id)
);
-- 优点:查询订单时无需 JOIN 用户表
-- 缺点:用户改名后订单表中的 user_name 不会自动更新

七、ER模型(实体-关系模型)

7.1 ER模型概述

ER模型(Entity-Relationship Model)是数据库概念设计的重要工具,用于描述现实世界中的实体、属性和它们之间的关系。

7.2 ER图的基本元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────┐
│ ER图元素说明 │
├─────────────────────────────────────────────┤
│ ┌─────────┐ │
│ │ 矩形 │ 表示实体(表) │
│ └─────────┘ │
│ ↓ │
│ ┌─────────┐ │
│ │ 椭圆形 │ 表示属性(字段) │
│ └─────────┘ │
│ ↓ │
│ ┌─────────┐ │
│ │ 菱形 │ 表示关系 │
│ └─────────┘ │
│ ↓ │
│ ━━━━━━━ 直线连接实体与关系 │
│ ↓ │
│ 1 : 1 一对一关系 │
│ 1 : N 一对多关系 │
│ M : N 多对多关系 │
└─────────────────────────────────────────────┘
图形 含义 示例
矩形 实体(表) 用户、订单、商品
椭圆形 属性(字段) 姓名、价格、日期
下划线 主键 用户ID、订单号
菱形 关系 购买、包含、属于
直线 连接 实体与关系相连

7.3 关系的三种类型

一对一(1:1)

1
2
3
4
5
6
7
┌─────────────┐      1      ┌─────────────┐
│ 用户 │◄───────────►│ 身份证 │
├─────────────┤ 1 ├─────────────┤
│ PK 用户ID │ │ PK 身份证ID │
│ 姓名 │ │ 身份证号 │
│ 手机号 │ │ 签发日期 │
└─────────────┘ └─────────────┘

示例:一个人只能有一个身份证,一个身份证只属于一个人。

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE tb_user (
user_id BIGINT PRIMARY KEY,
name VARCHAR(20),
phone VARCHAR(20)
);

CREATE TABLE tb_idcard (
card_id BIGINT PRIMARY KEY,
user_id BIGINT UNIQUE, -- UNIQUE 保证一对一
card_number VARCHAR(18),
issue_date DATE,
FOREIGN KEY (user_id) REFERENCES tb_user(user_id)
);

一对多(1:N)

1
2
3
4
5
6
7
8
┌─────────────┐      1      ┌─────────────┐
│ 部门 │◄────────────│ 员工 │
├─────────────┤ N ├─────────────┤
│ PK 部门ID │ │ PK 员工ID │
│ 部门名称 │ │ FK 部门ID │
│ 部门地址 │ │ 姓名 │
└─────────────┘ │ 工资 │
└─────────────┘

示例:一个部门有多个员工,一个员工只属于一个部门。

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE tb_department (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50),
location VARCHAR(100)
);

CREATE TABLE tb_employee (
emp_id BIGINT PRIMARY KEY,
dept_id INT,
name VARCHAR(20),
salary DECIMAL(10,2),
FOREIGN KEY (dept_id) REFERENCES tb_department(dept_id)
);

多对多(M:N)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
         ┌─────────────┐
│ 选课关系 │
├─────────────┤
M │ PK 学生ID │ N
◄────────│ PK 课程ID │────────►
│ 成绩 │
│ 选课时间 │
└─────────────┘
▲ ▲
│ │
┌──────┘ └──────┐
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ 学生 │ │ 课程 │
├─────────────┤ ├─────────────┤
│ PK 学生ID │ │ PK 课程ID │
│ 姓名 │ │ 课程名 │
│ 专业 │ │ 学分 │
└─────────────┘ └─────────────┘

示例:一个学生可以选多门课,一门课可以被多个学生选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TABLE tb_student (
student_id VARCHAR(10) PRIMARY KEY,
name VARCHAR(20),
major VARCHAR(50)
);

CREATE TABLE tb_course (
course_id VARCHAR(10) PRIMARY KEY,
course_name VARCHAR(50),
credit INT
);

-- 中间表(关联表)
CREATE TABLE tb_student_course (
student_id VARCHAR(10),
course_id VARCHAR(10),
score DECIMAL(5,2),
select_time DATETIME DEFAULT NOW(),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES tb_student(student_id),
FOREIGN KEY (course_id) REFERENCES tb_course(course_id)
);

7.4 ER模型设计步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 确定实体(Entities)
└─ 识别业务中的核心对象

2. 定义属性(Attributes)
└─ 为每个实体添加描述特征

3. 识别主键(Primary Key)
└─ 确定唯一标识每个实体的字段

4. 确定关系(Relationships)
└─ 分析实体之间的关联(1:1, 1:N, M:N)

5. 绘制ER图
└─ 使用标准符号绘制完整模型

6. 转换为数据表
└─ 按照转换原则生成表结构

7.5 ER模型转数据表的原则

规则 说明
一个实体 通常转换成一个数据表
一个多对多关系 通常转换成一个数据表(中间表)
一个1:1或1:N关系 往往通过表的外键来表达,不设计新表
属性 转换成表的字段

八、电商系统ER模型实战

8.1 确定实体

电商业务核心实体:

  • 用户(User)
  • 商品(Product)
  • 商品分类(Category)
  • 订单(Order)
  • 订单详情(OrderItem)
  • 购物车(Cart)
  • 收货地址(Address)
  • 评论(Review)

8.2 实体关系分析

关系 类型 说明
用户 → 地址 1:N 一个用户有多个地址
用户 → 购物车 1:1 一个用户只有一个购物车
用户 → 订单 1:N 一个用户有多个订单
用户 → 评论 1:N 一个用户发表多条评论
商品 → 评论 1:N 一个商品有多条评论
分类 → 商品 1:N 一个分类包含多种商品
订单 → 订单详情 1:N 一个订单包含多个详情
商品 ↔ 订单 M:N 通过订单详情关联

8.3 ER图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
             ┌─────────────┐
│ 商品分类 │
├─────────────┤
│ PK 分类ID │
│ 分类名称 │
│ FK 父分类ID │
└──────┬──────┘
│ 1

│ N
┌──────┴──────┐
│ 商品 │◄────────────────┐
├─────────────┤ │
│ PK 商品ID │ │
│ FK 分类ID │ │
│ 商品名称 │ │
│ 价格 │ │
│ 库存 │ │
│ 状态 │ │
└──────┬──────┘ │
│ │
┌────────────┼────────────┐ │
│ N │ N │ N │ 1
▼ ▼ ▼ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ 订单详情 │ │ 购物车 │ │ 评论 │ │
├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ PK 详情ID │ │ PK 购物车ID │ │ PK 评论ID │ │
│ FK 订单ID │ │ FK 用户ID │ │ FK 用户ID │ │
│ FK 商品ID │ │ FK 商品ID │ │ FK 商品ID │─┘
│ 数量 │ │ 数量 │ │ 内容 │
│ 单价 │ │ 加入时间 │ │ 评分 │
└──────┬──────┘ └─────────────┘ │ 评论时间 │
│ N └─────────────┘

│ 1
┌──────┴──────┐
│ 订单 │
├─────────────┤
│ PK 订单ID │
│ FK 用户ID │
│ 订单号 │
│ 总金额 │
│ 状态 │
│ 下单时间 │
└──────┬──────┘
│ N

│ 1
┌──────┴──────┐ ┌─────────────┐
│ 用户 │◄───────┤ 收货地址 │
├─────────────┤ 1 ├─────────────┤
│ PK 用户ID │ │ PK 地址ID │
│ 用户名 │ │ FK 用户ID │
│ 密码 │ │ 收件人 │
│ 手机号 │ │ 手机号 │
│ 邮箱 │ │ 省市区 │
│ 注册时间 │ │ 详细地址 │
└─────────────┘ │ 是否默认 │
└─────────────┘

8.4 转换为数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
-- 用户表
CREATE TABLE tb_user (
user_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
phone VARCHAR(20) COMMENT '手机号',
email VARCHAR(50) COMMENT '邮箱',
avatar VARCHAR(200) COMMENT '头像URL',
status TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用',
create_time DATETIME DEFAULT NOW() COMMENT '注册时间',
UNIQUE INDEX idx_username(username),
UNIQUE INDEX idx_phone(phone),
UNIQUE INDEX idx_email(email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 收货地址表
CREATE TABLE tb_address (
address_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '地址ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
receiver_name VARCHAR(20) COMMENT '收件人',
receiver_phone VARCHAR(20) COMMENT '收件人手机',
province VARCHAR(20) COMMENT '省',
city VARCHAR(20) COMMENT '市',
district VARCHAR(20) COMMENT '区',
detail_address VARCHAR(200) COMMENT '详细地址',
is_default TINYINT DEFAULT 0 COMMENT '是否默认:1是 0否',
INDEX idx_user_id(user_id),
FOREIGN KEY (user_id) REFERENCES tb_user(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址表';

-- 商品分类表
CREATE TABLE tb_category (
category_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '分类ID',
category_name VARCHAR(50) NOT NULL COMMENT '分类名称',
parent_id INT DEFAULT 0 COMMENT '父分类ID:0为一级分类',
sort_order INT DEFAULT 0 COMMENT '排序',
INDEX idx_parent_id(parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表';

-- 商品表
CREATE TABLE tb_product (
product_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID',
category_id INT COMMENT '分类ID',
product_name VARCHAR(100) NOT NULL COMMENT '商品名称',
price DECIMAL(10,2) NOT NULL COMMENT '价格',
stock INT DEFAULT 0 COMMENT '库存',
description TEXT COMMENT '商品描述',
main_image VARCHAR(200) COMMENT '主图',
status TINYINT DEFAULT 1 COMMENT '状态:1上架 0下架',
create_time DATETIME DEFAULT NOW() COMMENT '创建时间',
INDEX idx_category_id(category_id),
INDEX idx_status(status),
FOREIGN KEY (category_id) REFERENCES tb_category(category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

-- 购物车表
CREATE TABLE tb_cart (
cart_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '购物车ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
quantity INT DEFAULT 1 COMMENT '数量',
add_time DATETIME DEFAULT NOW() COMMENT '加入时间',
UNIQUE INDEX idx_user_product(user_id, product_id),
FOREIGN KEY (user_id) REFERENCES tb_user(user_id),
FOREIGN KEY (product_id) REFERENCES tb_product(product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表';

-- 订单表
CREATE TABLE tb_order (
order_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
order_no VARCHAR(50) NOT NULL COMMENT '订单号',
total_amount DECIMAL(10,2) COMMENT '总金额',
status VARCHAR(20) DEFAULT '待支付' COMMENT '状态',
receiver_name VARCHAR(20) COMMENT '收件人',
receiver_phone VARCHAR(20) COMMENT '收件人手机',
receiver_address VARCHAR(300) COMMENT '收货地址',
create_time DATETIME DEFAULT NOW() COMMENT '下单时间',
pay_time DATETIME COMMENT '支付时间',
UNIQUE INDEX idx_order_no(order_no),
INDEX idx_user_id(user_id),
INDEX idx_status(status),
FOREIGN KEY (user_id) REFERENCES tb_user(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

-- 订单详情表
CREATE TABLE tb_order_item (
item_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '详情ID',
order_id BIGINT NOT NULL COMMENT '订单ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
product_name VARCHAR(100) COMMENT '商品名称(快照)',
product_image VARCHAR(200) COMMENT '商品图片(快照)',
price DECIMAL(10,2) COMMENT '单价(快照)',
quantity INT COMMENT '数量',
total_price DECIMAL(10,2) COMMENT '小计',
INDEX idx_order_id(order_id),
FOREIGN KEY (order_id) REFERENCES tb_order(order_id),
FOREIGN KEY (product_id) REFERENCES tb_product(product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单详情表';

-- 评论表
CREATE TABLE tb_review (
review_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '评论ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
order_id BIGINT COMMENT '订单ID',
rating TINYINT COMMENT '评分:1-5',
content TEXT COMMENT '评论内容',
images VARCHAR(500) COMMENT '评论图片',
create_time DATETIME DEFAULT NOW() COMMENT '评论时间',
INDEX idx_product_id(product_id),
INDEX idx_user_id(user_id),
FOREIGN KEY (user_id) REFERENCES tb_user(user_id),
FOREIGN KEY (product_id) REFERENCES tb_product(product_id),
FOREIGN KEY (order_id) REFERENCES tb_order(order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论表';

九、数据库设计最佳实践

9.1 命名规范

对象 规范 示例
数据库 小写,下划线分隔 shop_db, user_center
表名 tb_前缀,小写,下划线分隔 tb_user, tb_order_item
字段名 小写,下划线分隔 user_id, create_time
索引名 idx_前缀 idx_user_id, idx_status
主键 表名缩写 + _id user_id, order_id
外键 fk_前缀 fk_order_user

9.2 字段设计规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 主键设计
-- 推荐使用 BIGINT UNSIGNED AUTO_INCREMENT
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT

-- 金额字段
-- 使用 DECIMAL,不用 FLOAT/DOUBLE
price DECIMAL(10,2) -- 最大 99999999.99

-- 状态字段
-- 使用 TINYINT,配合注释说明
status TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用'

-- 时间字段
-- 使用 DATETIME 或 TIMESTAMP
create_time DATETIME DEFAULT NOW()
update_time DATETIME DEFAULT NOW() ON UPDATE NOW()

-- 逻辑删除(推荐)
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除:1是 0否'
-- 而不是物理删除 DELETE

9.3 设计检查清单

  • 是否满足第一范式(列不可再分)
  • 是否满足第二范式(消除部分依赖)
  • 是否满足第三范式(消除传递依赖)
  • 每个表是否有主键
  • 外键是否建立索引
  • 字段类型是否合适
  • 是否有必要的唯一约束
  • 是否考虑逻辑删除
  • 是否添加表和字段注释
  • 是否选择合适的存储引擎

💡 小结:本章系统介绍了数据库设计规范,包括六大范式、三范式详解(1NF/2NF/3NF)、ER模型的绘制方法以及ER图转换为数据表的规则。通过电商系统实战案例,演示了从需求分析到表结构设计的完整过程。良好的数据库设计是应用系统稳定运行的基础。