{T}

关系模型

关系数据库建立在关系模型之上。关系模型本质上就是若干个存储数据的二维表,可以把它们看作很多 Excel 表。

基本概念

表的结构

概念说明示例
表(Table)存储数据的二维结构students 表
行(Row)一条记录(Record)一行代表一个学生
列(Column)一个字段(Field)name 列存储姓名

字段定义

字段定义包括:

  • 数据类型:整型、浮点型、字符串、日期等
  • 是否允许 NULL:NULL 表示字段数据不存在

重要NULL 不等于 0 或空字符串 ''。一个整型字段为 NULL 表示它的值不存在,而不是值为 0

NULL 的最佳实践

sql
-- 推荐:字段设置为 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

  1. 简化查询条件,无需判断 IS NULLIS NOT NULL
  2. 加快查询速度,索引效率更高
  3. 应用程序读取数据后无需判断是否为 NULL

表之间的关系

关系数据库的表和表之间需要建立"一对多"、"多对一"和"一对一"的关系,这样才能按照应用程序的逻辑组织和存储数据。

一对多关系

班级表 classes

ID名称班主任
201二年级一班王老师
202二年级二班李老师

学生表 students

ID姓名班级ID性别年龄
1小明201M9
2小红202F8
3小军202M8
4小白201F9

关系分析

  • 一个班级 → 多个学生(一对多)
  • 多个学生 → 一个班级(多对一)

一对一关系

教师表 teachers

ID名称年龄
A1王老师26
A2张老师39
A3李老师32
A4赵老师27

班级表 classes(只存储教师 ID):

ID名称班主任ID
201二年级一班A1
202二年级二班A3

关系分析:一个班级总是对应一个教师,班级表和教师表就是"一对一"关系。

为什么要拆分一对一关系

sql
-- 方案一:合并到一张表(适合简单场景)
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. 自增整数类型

sql
CREATE TABLE students (
    id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL
);

特点

  • 数据库自动分配自增整数
  • 无需担心主键重复
  • 查询效率高

容量限制

  • INT:约 21 亿条记录
  • BIGINT:约 922 亿亿条记录

2. 全局唯一 GUID 类型

sql
CREATE TABLE students (
    id VARCHAR(36) PRIMARY KEY,
    name VARCHAR(50) NOT NULL
);

-- Java 生成 GUID
// UUID.randomUUID().toString()
// 示例:8f55d96b-8acc-4636-8cb8-76bf8abc2f57

特点

  • 全局唯一,分布式系统友好
  • 可以在应用层预先生成
  • 占用空间较大(36 字符)

联合主键

联合主键是指两个或更多字段共同作为主键。

sql
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_numid_typename
1A张三
2A李四
2B王五

规则:联合主键的所有列组合起来不能重复,但单个列可以重复。

建议:没有必要的情况下尽量不使用联合主键,会增加复杂度。

外键

什么是外键

外键是用来建立表与表之间关系的字段。通过外键,可以在一个表中引用另一个表的记录。

外键约束

students 表

idclass_idnameother columns...
11小明...
21小红...
52小白...

classes 表

idnameother columns...
1一班...
2二班...

创建外键约束

sql
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 列

外键约束的作用

sql
-- 插入成功: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

删除外键约束

sql
ALTER TABLE students
DROP FOREIGN KEY fk_class_id;

注意:删除外键约束不会删除外键这一列。删除列需要使用 DROP COLUMN

外键的性能考量

sql
-- 方案一:使用外键约束(数据一致性由数据库保证)
ALTER TABLE students
ADD CONSTRAINT fk_class_id
FOREIGN KEY (class_id) REFERENCES classes (id);

-- 方案二:不使用外键约束(数据一致性由应用程序保证)
-- class_id 只是普通列,需要应用层确保数据有效性

对比

方案优点缺点
外键约束数据一致性强,自动校验降低性能,锁表风险
无外键约束性能更高,灵活性更好需要应用层保证一致性

互联网应用最佳实践:大部分互联网应用为了追求速度,不设置外键约束,而是通过应用程序保证逻辑正确性。

多对多关系

场景描述

一个老师可以对应多个班级,一个班级也可以对应多个老师。因此,班级表和老师表存在多对多关系。

实现方式

多对多关系通过中间表实现,中间表关联两个一对多关系。

teachers 表

idname
1张老师
2王老师
3李老师
4赵老师

classes 表

idname
1一班
2二班

中间表 teacher_class

idteacher_idclass_id
111
212
321
422
531
642

关系查询

teachers → classes

teacher_idteacher_nameclass_idclass_name
1张老师1, 2一班, 二班
2王老师1, 2一班, 二班
3李老师1一班
4赵老师2二班

classes → teachers

class_idclass_nameteacher_idteacher_name
1一班1, 2, 3张老师, 王老师, 李老师
2二班1, 2, 4张老师, 王老师, 赵老师

SQL 实现

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;

索引

什么是索引

索引是对某一列或多个列的值进行预排序的数据结构。通过索引,数据库系统可以直接定位到符合条件的记录,而不必扫描整个表。

创建索引

sql
-- 创建单列索引
ALTER TABLE students
ADD INDEX idx_score (score);

-- 创建多列索引(联合索引)
ALTER TABLE students
ADD INDEX idx_name_score (name, score);

索引的效率

索引效率取决于索引列的值是否散列(值的区分度)。

字段区分度是否适合建索引
id高(每条记录不同)✅ 非常适合
name中等(部分重复)✅ 适合
gender低(只有 M/F)❌ 不适合

索引选择性计算

sql
-- 选择性 = 不同值的数量 / 总记录数
-- 选择性越接近 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;

索引的优缺点

优点缺点
大幅提高查询速度占用存储空间
加速排序和分组插入/更新/删除时需要维护索引
主键自动创建索引索引过多会降低写入性能

主键索引

关系数据库会自动对主键创建索引,主键索引效率最高。

sql
-- 主键自动创建索引,无需手动创建
CREATE TABLE students (
    id BIGINT PRIMARY KEY,  -- 自动创建主键索引
    name VARCHAR(50)
);

唯一索引

唯一索引保证列的值唯一,同时提供索引功能。

sql
-- 方式一:创建唯一索引
ALTER TABLE students
ADD UNIQUE INDEX uni_name (name);

-- 方式二:添加唯一约束(不创建索引)
ALTER TABLE students
ADD CONSTRAINT uni_name UNIQUE (name);

区别

  • UNIQUE INDEX:创建索引 + 唯一约束
  • UNIQUE CONSTRAINT:仅添加唯一约束,无索引

索引使用建议

sql
-- 适合创建索引的场景
-- 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

完整示例

学生管理系统数据模型

sql
-- 班级表
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 约束用户-用户详情
一对多外键班级-学生
多对多中间表学生-课程

设计原则

  1. 主键选择:使用业务无关字段,推荐自增 BIGINT
  2. 避免 NULL:字段尽量设置为 NOT NULL
  3. 合理使用索引:高选择性列建索引,避免过度索引
  4. 外键权衡:数据一致性 vs 性能,互联网应用倾向不用外键约束