跳到主要内容

关系模型

问题

什么是关系模型?关系型数据库中的表、行、列、主键、外键分别是什么?

答案

关系模型(Relational Model) 是由 IBM 研究员 Edgar F. Codd 在 1970 年提出的数据模型,是目前最广泛使用的数据库理论基础。关系型数据库(如 MySQL、PostgreSQL、Oracle)都基于关系模型构建。

简单来说,关系模型用二维表格来组织数据——你可以把它理解为一个结构化的 Excel 表格。


核心概念

关系(Relation)= 表(Table)

一个关系就是一张。例如,存储用户信息的表 users

idnameemailage
1张三zhang@example.com25
2李四li@example.com30
3王五wang@example.com28

元组(Tuple)= 行(Row)= 记录(Record)

表中的每一行就是一个元组,代表一条具体的数据记录。例如 (1, '张三', 'zhang@example.com', 25) 是一个元组。

属性(Attribute)= 列(Column)= 字段(Field)

表中的每一列就是一个属性,代表数据的一个特征维度。例如 nameemailage 都是属性。

域(Domain)

每个属性的取值范围叫做域。例如:

  • age 的域可以是 0~150 的整数
  • email 的域是 符合邮箱格式的字符串

键(Key)

键是关系模型中最重要的概念之一,用来唯一标识记录和建立表之间的关联。

主键(Primary Key)

主键是表中能唯一标识每一行记录的列(或列的组合)。

规则:

  • 每张表只能有一个主键
  • 主键的值不能为空(NOT NULL)
  • 主键的值不能重复(UNIQUE)
-- 创建表时指定主键
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT, -- id 是主键,自增
name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE
);
主键选择建议
  • 推荐:使用自增整数 id 或 UUID 作为主键(称为"代理键")
  • 不推荐:使用业务字段(如手机号、身份证号)作为主键——业务规则可能变化

外键(Foreign Key)

外键是一张表中引用另一张表主键的列,用来建立表与表之间的关联

-- orders 表的 user_id 是外键,引用 users 表的 id
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
product VARCHAR(100),
amount DECIMAL(10, 2),
FOREIGN KEY (user_id) REFERENCES users(id)
);

对应关系如图:

外键级联操作

当主表的数据被删除或更新时,外键约束可以自动处理从表的关联数据:

CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
product VARCHAR(100),
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE -- 删除用户时,自动删除其所有订单
ON UPDATE CASCADE -- 更新用户 id 时,自动更新订单中的 user_id
);
级联操作说明适用场景
CASCADE主表删除/更新时,从表跟着删除/更新用户删除 → 删除所有订单
SET NULL主表删除/更新时,从表外键设为 NULL员工离职 → 其负责项目的 manager_id 设空
SET DEFAULT主表删除/更新时,从表外键设为默认值较少使用
RESTRICT主表有关联数据时禁止操作(默认行为)防止误删有订单的用户
NO ACTION与 RESTRICT 类似(在 MySQL 中等同)标准 SQL 写法
-- 实际例子:员工表和部门表
CREATE TABLE departments (
id INT PRIMARY KEY,
name VARCHAR(50)
);

CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT,
-- 删除部门时,员工的 dept_id 设为 NULL(而不是删除员工)
FOREIGN KEY (dept_id) REFERENCES departments(id) ON DELETE SET NULL
);
级联删除要谨慎

ON DELETE CASCADE 虽然方便,但可能导致意外的大规模数据删除。例如删除一个分类,可能级联删除该分类下上万条商品记录。生产环境中建议使用软删除(逻辑删除)代替级联删除。

其他键类型

键类型说明示例
候选键(Candidate Key)能唯一标识记录的最小属性集,表中可以有多个idemail(假设 email 也唯一)
超键(Super Key)包含候选键的任意属性集合{id}{id, name}{email, age}
主键(Primary Key)从候选键中选出一个作为主键id 作为主键
外键(Foreign Key)引用其他表主键的列orders.user_idusers.id
复合键(Composite Key)由多个列组成的键(student_id, course_id) 作为选课表的主键

关系之间的联系

表与表之间有三种基本关系:

一对一(1:1)

一个用户对应一条详细信息。通常可以合并为一张表,但出于性能考虑可能拆分。

-- 用户基本信息(高频访问)
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);

-- 用户详细信息(低频访问,拆分存储)
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY, -- 同时也是外键
address VARCHAR(200),
bio TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);

一对多(1:N

最常见的关系——一个用户可以有多个订单。

-- 一个用户 → 多个订单
-- 在"多"的一方加外键
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT, -- 外键指向 users
amount DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id)
);

多对多(M:N

一个学生可以选多门课,一门课可以有多个学生。需要通过中间表(关联表) 来实现:

-- 学生表
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(50)
);

-- 课程表
CREATE TABLE courses (
id INT PRIMARY KEY,
title VARCHAR(100)
);

-- 中间表:选课记录
CREATE TABLE enrollments (
student_id INT,
course_id INT,
enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (student_id, course_id), -- 复合主键
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);

关系代数(了解)

关系代数是关系模型的理论基础,定义了对关系(表)的基本操作。SQL 语句本质上就是关系代数的实现:

关系代数操作对应 SQL说明
选择(σ)WHERE过滤满足条件的行
投影(π)SELECT 列名选择需要的列
并(∪)UNION合并两个结果集
差(−)EXCEPT在 A 中但不在 B 中
笛卡尔积(×)CROSS JOIN两表所有行的组合
连接(⋈)JOIN按条件合并两表
除(÷)需要组合实现满足所有关联条件的行
重命名(ρ)AS 别名给表或列起别名

关系代数到 SQL 的映射示例

-- 关系代数:π(name, email) σ(age > 25)(users)
-- 含义:从 users 表中选择 age > 25 的记录,只取 name 和 email 列
SELECT name, email -- 投影(π)
FROM users
WHERE age > 25; -- 选择(σ)

-- 关系代数:users ⋈(users.id = orders.user_id) orders
-- 含义:将 users 和 orders 按 id 连接
SELECT *
FROM users
JOIN orders ON users.id = orders.user_id; -- 连接(⋈)

数据库 Schema

Schema(模式) 是数据库的逻辑结构定义,它描述了数据库中有哪些表、每张表有哪些列、列的类型是什么等。

三层模式结构

层次说明对应
外模式(External Schema)不同用户看到的数据视图SQL VIEW
概念模式(Conceptual Schema)数据库的逻辑结构(表、列、约束)CREATE TABLE
内模式(Internal Schema)数据在磁盘上的物理存储方式存储引擎(InnoDB)

这种三层架构的好处是数据独立性:修改物理存储方式不影响逻辑结构,修改逻辑结构不影响用户视图。

Schema 在不同数据库中的含义

-- MySQL 中 schema ≈ database
CREATE DATABASE myapp;
USE myapp;

-- PostgreSQL 中 schema 是 database 下的命名空间
CREATE SCHEMA sales;
CREATE TABLE sales.orders (...); -- 在 sales 模式下创建表

视图(View)

视图是基于 SQL 查询结果的虚拟表。它不存储数据,每次查询时动态生成。

-- 创建视图:活跃用户(最近30天有登录的用户)
CREATE VIEW active_users AS
SELECT u.id, u.name, u.email, MAX(l.login_time) AS last_login
FROM users u
JOIN login_logs l ON u.id = l.user_id
WHERE l.login_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY u.id, u.name, u.email;

-- 使用视图,就像查询普通表一样
SELECT * FROM active_users WHERE name LIKE '张%';

视图的用途

  • 简化复杂查询:把常用的复杂 JOIN 封装为视图
  • 权限控制:只让用户看到部分列(如隐藏薪资字段)
  • 数据抽象:底层表结构变化时,通过修改视图保持接口不变

NULL 在关系模型中的处理

NULL 表示"未知"或"不适用",它在关系模型中有特殊的行为,是面试和实际开发中的常见坑点。

NULL 的三值逻辑

普通布尔逻辑只有 TRUE 和 FALSE,但 NULL 引入了第三种值——UNKNOWN

表达式结果
NULL = NULLUNKNOWN(不是 TRUE!)
NULL != NULLUNKNOWN
NULL > 5UNKNOWN
NULL AND TRUEUNKNOWN
NULL OR TRUETRUE
NULL AND FALSEFALSE
NOT NULLUNKNOWN
-- ❌ 错误写法:WHERE 不会返回 UNKNOWN 的行
SELECT * FROM users WHERE age = NULL; -- 永远返回空结果!

-- ✅ 正确写法:使用 IS NULL / IS NOT NULL
SELECT * FROM users WHERE age IS NULL;
SELECT * FROM users WHERE age IS NOT NULL;

NULL 对聚合函数的影响

-- 数据:scores = [90, NULL, 80, NULL, 70]
SELECT COUNT(*) FROM t; -- 5(计算所有行,包括 NULL)
SELECT COUNT(score) FROM t; -- 3(只计算非 NULL 值)
SELECT SUM(score) FROM t; -- 240(忽略 NULL)
SELECT AVG(score) FROM t; -- 80(= 240 / 3,不是 240 / 5)
NULL 常见陷阱
  1. NOT IN 遇到 NULL 会返回空结果:WHERE id NOT IN (1, 2, NULL) → 空!
  2. DISTINCT 会把多个 NULL 视为相同值
  3. ORDER BY 中 NULL 的排序位置各数据库不同

实战:电商数据库设计示例

把上面学到的概念串起来,设计一个简单的电商数据库:

-- 用户表
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 商品分类表(自引用——树形结构)
CREATE TABLE categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
parent_id INT, -- 自引用外键,指向父分类
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);

-- 商品表
CREATE TABLE products (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
stock INT NOT NULL DEFAULT 0 CHECK (stock >= 0),
category_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);

-- 订单表
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
total_amount DECIMAL(12, 2) NOT NULL,
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);

-- 订单明细表(多对多:订单 ↔ 商品)
CREATE TABLE order_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
unit_price DECIMAL(10, 2) NOT NULL, -- 下单时的价格快照
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);

常见面试问题

Q1: 什么是关系模型?它的核心特点是什么?

答案

关系模型是用二维表(关系) 来组织和管理数据的模型。核心特点:

  1. 数据以表格形式存储:每张表有固定的列(属性)和不定数量的行(记录)
  2. 行无序:行的存储顺序不影响查询结果
  3. 列有类型:每列有固定的数据类型(如 INT、VARCHAR)
  4. 通过键来关联:表与表之间通过主键-外键建立联系
  5. 操作基于关系代数:提供 SELECT、JOIN 等操作

Q2: 主键和唯一键有什么区别?

答案

对比项主键(PRIMARY KEY)唯一键(UNIQUE)
数量每表只能有 1 个可以有多个
NULL不允许 NULL允许 NULL(通常只允许一个 NULL)
索引自动创建聚簇索引自动创建非聚簇索引
用途唯一标识行的主要方式保证列值不重复
CREATE TABLE users (
id INT PRIMARY KEY, -- 主键:不能为空,不能重复
email VARCHAR(100) UNIQUE, -- 唯一键:不能重复,可以为空
phone VARCHAR(20) UNIQUE -- 另一个唯一键
);

Q3: 什么是外键?使用外键有什么优缺点?

答案

外键是一个表中的列,它引用另一个表的主键,用来维护表之间的参照完整性

优点

  • 自动保证数据一致性(不能插入不存在的引用值)
  • 支持级联操作(删除用户时自动删除其订单)
  • 代码层面更清晰的关系表达

缺点

  • 影响写入性能:每次 INSERT/UPDATE 都需要验证外键约束
  • 增加锁竞争:外键检查需要对被引用的表加锁
  • 不利于分库分表:跨库的外键约束无法实现
生产环境实践

很多互联网公司在生产环境中不使用数据库外键约束,而是在应用层(代码层面)来保证数据一致性。原因是大规模系统中外键的性能和运维成本过高。但在面试中你要清楚外键的概念和作用。

Q4: 什么是复合主键?什么时候使用?

答案

复合主键是由多个列组合而成的主键。当单个列无法唯一标识一行记录时使用。

典型场景是多对多关系的中间表

-- 选课表:一个学生 + 一门课 = 唯一的选课记录
CREATE TABLE enrollments (
student_id INT,
course_id INT,
grade DECIMAL(3,1),
PRIMARY KEY (student_id, course_id) -- 复合主键
);

student_id 单独不唯一(一个学生选多门课),course_id 单独也不唯一(一门课有多个学生),但组合起来就是唯一的。

Q5: 一对一、一对多、多对多关系分别怎么实现?

答案

关系类型实现方式示例
一对一在任一方加外键(通常在从表上加),或合并为一张表用户 ↔ 用户详情
一对多在"多"的一方添加外键,指向"一"的主键用户 → 订单
多对多创建中间表(关联表),两个外键分别指向两张主表学生 ↔ 课程

Q6: 自然键和代理键有什么区别?推荐用哪种?

答案

  • 自然键(Natural Key):使用有业务含义的字段作为主键,如身份证号、手机号
  • 代理键(Surrogate Key):使用无业务意义的自增 ID 或 UUID 作为主键
对比自然键代理键
含义有业务意义无业务意义
稳定性可能因业务规则变化而修改稳定不变
存储可能较长(如身份证 18 位)通常较短(INT 4 字节)
JOIN 性能可能更慢(字符串比较)更快(整数比较)

推荐使用代理键:自增 INT(单机)或 BIGINT(高并发)。UUID 适合分布式场景,但存储占用更大且对索引不友好。

Q7: NULL 和空字符串有什么区别?

答案

对比NULL空字符串 ''
含义未知、不存在已知,但值为空
存储不占用实际数据空间(只有标志位)占用 0~1 字节
比较NULL = NULL → UNKNOWN'' = '' → TRUE
判断IS NULL / IS NOT NULL= '' / != ''
聚合COUNT(col) 不计入COUNT(col) 计入
-- 实际开发建议:尽量避免 NULL,给字段设默认值
ALTER TABLE users MODIFY phone VARCHAR(20) NOT NULL DEFAULT '';

Q8: 什么是数据库视图?视图和表有什么区别?

答案

视图是基于 SQL 查询定义的虚拟表,不实际存储数据。

对比表(Table)视图(View)
数据存储真实存储在磁盘上不存储数据,每次查询时动态执行
增删改完全支持简单视图可以,复杂视图不行
性能直接读取每次需要执行底层 SQL
索引可以创建索引一般不能(物化视图除外)
用途存储业务数据简化查询、权限控制、数据抽象

Q9: 什么是自引用外键?给一个例子

答案

自引用外键是指表中的某一列引用同一张表的主键,用来表示树形/层级结构

-- 部门表:每个部门有一个上级部门
CREATE TABLE departments (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
parent_id INT, -- 自引用:指向同一张表的 id
FOREIGN KEY (parent_id) REFERENCES departments(id)
);

INSERT INTO departments VALUES (1, '总公司', NULL); -- 根节点
INSERT INTO departments VALUES (2, '技术部', 1); -- 一级部门
INSERT INTO departments VALUES (3, '前端组', 2); -- 二级部门
INSERT INTO departments VALUES (4, '后端组', 2); -- 二级部门

常见场景:部门层级、商品分类、评论回复、地区树(省-市-区)等。

Q10: 级联删除(ON DELETE CASCADE)有什么风险?

答案

主要风险:

  1. 意外的大规模删除:删除一条父记录可能级联删除数十万条子记录
  2. 难以追踪:应用层日志可能无法感知数据库级联删除的操作
  3. 性能问题:级联删除大量数据时会产生大量 redo log,可能阻塞其他操作

生产环境推荐做法

  • 使用软删除(逻辑删除):添加 deleted_atis_deleted 字段
  • 需要物理删除时,在应用层显式删除,可以加事务、写日志
  • RESTRICT 代替 CASCADE,防止误操作

Q11: 为什么很多互联网公司不用外键约束?

答案

  1. 性能影响:每次 INSERT/UPDATE/DELETE 都要检查外键约束,高并发下是瓶颈
  2. 锁竞争:外键检查需要对被引用的表加共享锁,影响写入并发
  3. 分库分表困难:跨数据库实例的外键约束无法实现
  4. 运维不便:DDL 操作(如加字段)需要考虑外键依赖关系

替代方案:在应用层通过代码逻辑保证数据一致性,并用定时任务或数据校验脚本发现不一致数据。

Q12: 关系模型和文档模型有什么本质区别?

答案

对比关系模型文档模型(如 MongoDB)
数据结构二维表格(行+列)JSON/BSON 文档(支持嵌套)
Schema固定 Schema(写入前定义)灵活 Schema(写入时决定)
关联通过 JOIN 连接多表通过嵌套或引用(无 JOIN)
事务完善的 ACID 事务单文档原子性(多文档事务需 4.0+)
扩展垂直扩展为主水平扩展(分片)更方便
适合场景结构化数据、强一致性半结构化数据、快速迭代
-- 关系模型:用户和地址分开存
-- users: {id, name, email}
-- addresses: {id, user_id, city, street}

-- 文档模型:嵌套在一起
-- { name: "张三", email: "...", addresses: [{city: "北京", street: "..."}] }

相关链接