关系模型
关系数据库建立在关系模型之上。关系模型本质上就是若干个存储数据的二维表,可以把它们看作很多 Excel 表。
基本概念
表的结构
| 概念 | 说明 | 示例 |
|---|---|---|
| 表(Table) | 存储数据的二维结构 | students 表 |
| 行(Row) | 一条记录(Record) | 一行代表一个学生 |
| 列(Column) | 一个字段(Field) | name 列存储姓名 |
字段定义
字段定义包括:
- 数据类型:整型、浮点型、字符串、日期等
- 是否允许 NULL:NULL 表示字段数据不存在
重要:
NULL不等于0或空字符串''。一个整型字段为NULL表示它的值不存在,而不是值为0。
NULL 的最佳实践:
-- 推荐:字段设置为 NOT NULL
CREATE TABLE students (
id BIGINT NOT NULL,
name VARCHAR(50) NOT NULL,
score INT NOT NULL DEFAULT 0
);
-- 不推荐:允许 NULL 会增加查询复杂度
CREATE TABLE students (
id BIGINT,
name VARCHAR(50),
score INT
);为什么避免 NULL:
- 简化查询条件,无需判断
IS NULL或IS NOT NULL - 加快查询速度,索引效率更高
- 应用程序读取数据后无需判断是否为 NULL
表之间的关系
关系数据库的表和表之间需要建立"一对多"、"多对一"和"一对一"的关系,这样才能按照应用程序的逻辑组织和存储数据。
一对多关系
班级表 classes:
| ID | 名称 | 班主任 |
|---|---|---|
| 201 | 二年级一班 | 王老师 |
| 202 | 二年级二班 | 李老师 |
学生表 students:
| ID | 姓名 | 班级ID | 性别 | 年龄 |
|---|---|---|---|---|
| 1 | 小明 | 201 | M | 9 |
| 2 | 小红 | 202 | F | 8 |
| 3 | 小军 | 202 | M | 8 |
| 4 | 小白 | 201 | F | 9 |
关系分析:
- 一个班级 → 多个学生(一对多)
- 多个学生 → 一个班级(多对一)
一对一关系
教师表 teachers:
| ID | 名称 | 年龄 |
|---|---|---|
| A1 | 王老师 | 26 |
| A2 | 张老师 | 39 |
| A3 | 李老师 | 32 |
| A4 | 赵老师 | 27 |
班级表 classes(只存储教师 ID):
| ID | 名称 | 班主任ID |
|---|---|---|
| 201 | 二年级一班 | A1 |
| 202 | 二年级二班 | A3 |
关系分析:一个班级总是对应一个教师,班级表和教师表就是"一对一"关系。
为什么要拆分一对一关系:
-- 方案一:合并到一张表(适合简单场景)
CREATE TABLE classes (
id INT PRIMARY KEY,
name VARCHAR(50),
teacher_name VARCHAR(50),
teacher_age INT
);
-- 方案二:拆分为两张表(适合复杂场景)
-- 优点:把经常读取和不经常读取的字段分开,提高查询性能
CREATE TABLE classes (
id INT PRIMARY KEY,
name VARCHAR(50),
teacher_id VARCHAR(10)
);
CREATE TABLE teachers (
id VARCHAR(10) PRIMARY KEY,
name VARCHAR(50),
age INT,
phone VARCHAR(20),
email VARCHAR(100)
);主键
什么是主键
在关系数据库中,一张表中的每一行数据被称为一条记录。一条记录由多个字段组成。
主键的作用:唯一区分不同的记录,任意两条记录的主键不能相同。
主键的选择原则
基本原则:不使用任何业务相关的字段作为主键。
| 字段类型 | 是否适合做主键 | 原因 |
|---|---|---|
| 身份证号 | ❌ 不适合 | 业务字段,可能升位或变更 |
| 手机号 | ❌ 不适合 | 业务字段,可能更换 |
| 邮箱地址 | ❌ 不适合 | 业务字段,可能变更 |
| 自增 ID | ✅ 适合 | 完全业务无关,数据库自动维护 |
| GUID | ✅ 适合 | 全局唯一,分布式系统友好 |
主键类型
1. 自增整数类型
CREATE TABLE students (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL
);特点:
- 数据库自动分配自增整数
- 无需担心主键重复
- 查询效率高
容量限制:
INT:约 21 亿条记录BIGINT:约 922 亿亿条记录
2. 全局唯一 GUID 类型
CREATE TABLE students (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
-- Java 生成 GUID
// UUID.randomUUID().toString()
// 示例:8f55d96b-8acc-4636-8cb8-76bf8abc2f57特点:
- 全局唯一,分布式系统友好
- 可以在应用层预先生成
- 占用空间较大(36 字符)
联合主键
联合主键是指两个或更多字段共同作为主键。
CREATE TABLE user_credentials (
id_num INT NOT NULL,
id_type VARCHAR(10) NOT NULL,
name VARCHAR(50),
PRIMARY KEY (id_num, id_type)
);示例数据:
| id_num | id_type | name |
|---|---|---|
| 1 | A | 张三 |
| 2 | A | 李四 |
| 2 | B | 王五 |
规则:联合主键的所有列组合起来不能重复,但单个列可以重复。
建议:没有必要的情况下尽量不使用联合主键,会增加复杂度。
外键
什么是外键
外键是用来建立表与表之间关系的字段。通过外键,可以在一个表中引用另一个表的记录。
外键约束
students 表:
| id | class_id | name | other columns... |
|---|---|---|---|
| 1 | 1 | 小明 | ... |
| 2 | 1 | 小红 | ... |
| 5 | 2 | 小白 | ... |
classes 表:
| id | name | other columns... |
|---|---|---|
| 1 | 一班 | ... |
| 2 | 二班 | ... |
创建外键约束:
ALTER TABLE students
ADD CONSTRAINT fk_class_id
FOREIGN KEY (class_id)
REFERENCES classes (id);语法解析:
| 部分 | 说明 |
|---|---|
fk_class_id | 外键约束名称,可自定义 |
FOREIGN KEY (class_id) | 指定外键字段 |
REFERENCES classes (id) | 关联到 classes 表的 id 列 |
外键约束的作用
-- 插入成功:classes 表存在 id=1 的记录
INSERT INTO students (id, class_id, name) VALUES (1, 1, '小明');
-- 插入失败:classes 表不存在 id=99 的记录
INSERT INTO students (id, class_id, name) VALUES (2, 99, '小红');
-- ERROR: Cannot add or update a child row: a foreign key constraint fails删除外键约束
ALTER TABLE students
DROP FOREIGN KEY fk_class_id;注意:删除外键约束不会删除外键这一列。删除列需要使用
DROP COLUMN。
外键的性能考量
-- 方案一:使用外键约束(数据一致性由数据库保证)
ALTER TABLE students
ADD CONSTRAINT fk_class_id
FOREIGN KEY (class_id) REFERENCES classes (id);
-- 方案二:不使用外键约束(数据一致性由应用程序保证)
-- class_id 只是普通列,需要应用层确保数据有效性对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 外键约束 | 数据一致性强,自动校验 | 降低性能,锁表风险 |
| 无外键约束 | 性能更高,灵活性更好 | 需要应用层保证一致性 |
互联网应用最佳实践:大部分互联网应用为了追求速度,不设置外键约束,而是通过应用程序保证逻辑正确性。
多对多关系
场景描述
一个老师可以对应多个班级,一个班级也可以对应多个老师。因此,班级表和老师表存在多对多关系。
实现方式
多对多关系通过中间表实现,中间表关联两个一对多关系。
teachers 表:
| id | name |
|---|---|
| 1 | 张老师 |
| 2 | 王老师 |
| 3 | 李老师 |
| 4 | 赵老师 |
classes 表:
| id | name |
|---|---|
| 1 | 一班 |
| 2 | 二班 |
中间表 teacher_class:
| id | teacher_id | class_id |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 3 | 1 |
| 6 | 4 | 2 |
关系查询
teachers → classes:
| teacher_id | teacher_name | class_id | class_name |
|---|---|---|---|
| 1 | 张老师 | 1, 2 | 一班, 二班 |
| 2 | 王老师 | 1, 2 | 一班, 二班 |
| 3 | 李老师 | 1 | 一班 |
| 4 | 赵老师 | 2 | 二班 |
classes → teachers:
| class_id | class_name | teacher_id | teacher_name |
|---|---|---|---|
| 1 | 一班 | 1, 2, 3 | 张老师, 王老师, 李老师 |
| 2 | 二班 | 1, 2, 4 | 张老师, 王老师, 赵老师 |
SQL 实现
-- 创建中间表
CREATE TABLE teacher_class (
id INT PRIMARY KEY AUTO_INCREMENT,
teacher_id INT NOT NULL,
class_id INT NOT NULL,
FOREIGN KEY (teacher_id) REFERENCES teachers(id),
FOREIGN KEY (class_id) REFERENCES classes(id),
UNIQUE KEY uk_teacher_class (teacher_id, class_id)
);
-- 查询某老师教授的所有班级
SELECT c.*
FROM classes c
JOIN teacher_class tc ON c.id = tc.class_id
WHERE tc.teacher_id = 1;
-- 查询某班级的所有老师
SELECT t.*
FROM teachers t
JOIN teacher_class tc ON t.id = tc.teacher_id
WHERE tc.class_id = 1;索引
什么是索引
索引是对某一列或多个列的值进行预排序的数据结构。通过索引,数据库系统可以直接定位到符合条件的记录,而不必扫描整个表。
创建索引
-- 创建单列索引
ALTER TABLE students
ADD INDEX idx_score (score);
-- 创建多列索引(联合索引)
ALTER TABLE students
ADD INDEX idx_name_score (name, score);索引的效率
索引效率取决于索引列的值是否散列(值的区分度)。
| 字段 | 区分度 | 是否适合建索引 |
|---|---|---|
| id | 高(每条记录不同) | ✅ 非常适合 |
| name | 中等(部分重复) | ✅ 适合 |
| gender | 低(只有 M/F) | ❌ 不适合 |
索引选择性计算:
-- 选择性 = 不同值的数量 / 总记录数
-- 选择性越接近 1,索引效率越高
SELECT
COUNT(DISTINCT gender) / COUNT(*) AS gender_selectivity,
COUNT(DISTINCT name) / COUNT(*) AS name_selectivity,
COUNT(DISTINCT id) / COUNT(*) AS id_selectivity
FROM students;索引的优缺点
| 优点 | 缺点 |
|---|---|
| 大幅提高查询速度 | 占用存储空间 |
| 加速排序和分组 | 插入/更新/删除时需要维护索引 |
| 主键自动创建索引 | 索引过多会降低写入性能 |
主键索引
关系数据库会自动对主键创建索引,主键索引效率最高。
-- 主键自动创建索引,无需手动创建
CREATE TABLE students (
id BIGINT PRIMARY KEY, -- 自动创建主键索引
name VARCHAR(50)
);唯一索引
唯一索引保证列的值唯一,同时提供索引功能。
-- 方式一:创建唯一索引
ALTER TABLE students
ADD UNIQUE INDEX uni_name (name);
-- 方式二:添加唯一约束(不创建索引)
ALTER TABLE students
ADD CONSTRAINT uni_name UNIQUE (name);区别:
UNIQUE INDEX:创建索引 + 唯一约束UNIQUE CONSTRAINT:仅添加唯一约束,无索引
索引使用建议
-- 适合创建索引的场景
-- 1. WHERE 条件中频繁使用的列
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
-- 2. JOIN 关联的列
ALTER TABLE order_items ADD INDEX idx_order_id (order_id);
-- 3. ORDER BY 排序的列
ALTER TABLE products ADD INDEX idx_price (price);
-- 4. 联合索引(遵循最左前缀原则)
ALTER TABLE users ADD INDEX idx_name_age (name, age);
-- 可以使用:WHERE name = '张三'
-- 可以使用:WHERE name = '张三' AND age = 20
-- 不能使用:WHERE age = 20完整示例
学生管理系统数据模型
-- 班级表
CREATE TABLE classes (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL COMMENT '班级名称',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) COMMENT '班级信息表';
-- 教师表
CREATE TABLE teachers (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL COMMENT '教师姓名',
age INT COMMENT '年龄',
phone VARCHAR(20) COMMENT '联系电话'
) COMMENT '教师信息表';
-- 学生表
CREATE TABLE students (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL COMMENT '学生姓名',
class_id INT NOT NULL COMMENT '班级ID',
gender CHAR(1) DEFAULT 'M' COMMENT '性别:M-男,F-女',
score INT DEFAULT 0 COMMENT '分数',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_class_id (class_id),
INDEX idx_score (score)
) COMMENT '学生信息表';
-- 班级-教师关联表(多对多)
CREATE TABLE class_teacher (
id INT PRIMARY KEY AUTO_INCREMENT,
class_id INT NOT NULL,
teacher_id INT NOT NULL,
UNIQUE KEY uk_class_teacher (class_id, teacher_id)
) COMMENT '班级教师关联表';总结
核心概念
| 概念 | 说明 |
|---|---|
| 主键 | 唯一标识一条记录,推荐使用自增 ID 或 GUID |
| 外键 | 建立表间关系,互联网应用通常不使用外键约束 |
| 索引 | 加速查询,但会降低写入性能 |
关系类型
| 关系类型 | 实现方式 | 示例 |
|---|---|---|
| 一对一 | 外键 + UNIQUE 约束 | 用户-用户详情 |
| 一对多 | 外键 | 班级-学生 |
| 多对多 | 中间表 | 学生-课程 |
设计原则
- 主键选择:使用业务无关字段,推荐自增 BIGINT
- 避免 NULL:字段尽量设置为 NOT NULL
- 合理使用索引:高选择性列建索引,避免过度索引
- 外键权衡:数据一致性 vs 性能,互联网应用倾向不用外键约束