乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

外键

导学

关系型数据库的"关系"不仅体现在 JOIN 查询上,更体现在数据一致性约束上。外键(Foreign Key)是确保引用完整性的核心机制——防止出现"订单指向不存在的用户"这类脏数据。

定义

外键(Foreign Key):在一个表中建立一个或多个列,引用另一个表的主键(或唯一键)。外键约束确保引用关系的数据完整性,防止出现无效的关联数据。

核心语法

建表时定义外键

当前数据状态:先创建父表

CREATE TABLE departments (
    dept_id INT PRIMARY KEY AUTO_INCREMENT,
    dept_name VARCHAR(20) NOT NULL
);

INSERT INTO departments (dept_name) VALUES
('技术部'),
('产品部');

SELECT * FROM departments;
dept_iddept_name
1技术部
2产品部

操作语句:

-- 创建子表,带外键约束
CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept_id INT,
    score DECIMAL(5,2),
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);

INSERT INTO employees (emp_name, dept_id, score) VALUES
('大翔', 1, 100),
('白歌', 1, NULL);

SELECT * FROM employees;

操作后数据状态:

emp_idemp_namedept_idscore
1大翔1100
2白歌1NULL

结果解读:dept_id 列引用了 departments(dept_id)。子表插入时,MySQL 会自动检查 dept_id 是否在父表中存在。

插入违反外键的数据

当前数据状态:departments 表只有 dept_id 为 1、2 的记录。

操作语句:

-- 尝试插入不存在的 dept_id
INSERT INTO employees (emp_name, dept_id, score) VALUES ('小崔', 999, 90);

操作后数据状态:

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
SELECT * FROM employees;
emp_idemp_namedept_idscore
1大翔1100
2白歌1NULL

结果解读:插入被拒绝!dept_id=999 在 departments 表中不存在,外键约束阻止了这条脏数据的插入。

建表后添加外键

当前数据状态:创建一张没有外键的表

CREATE TABLE scores (
    score_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_id INT,
    score DECIMAL(5,2)
);

INSERT INTO scores (emp_id, score) VALUES
(1, 90),
(2, 85);

操作语句:

ALTER TABLE scores
ADD CONSTRAINT fk_emp
FOREIGN KEY (emp_id) REFERENCES employees(emp_id);

SHOW CREATE TABLE scores;

操作后数据状态(节选):

CREATE TABLE `scores` (
  `score_id` int(11) NOT NULL AUTO_INCREMENT,
  `emp_id` int(11) DEFAULT NULL,
  `score` decimal(5,2) DEFAULT NULL,
  PRIMARY KEY (`score_id`),
  KEY `fk_emp` (`emp_id`),
  CONSTRAINT `fk_emp` FOREIGN KEY (`emp_id`) REFERENCES `employees` (`emp_id`)
) ENGINE=InnoDB

结果解读:MySQL 自动为外键列 emp_id 创建了索引 fk_emp,这是 MySQL 外键的强制要求(没有索引的外键列会自动创建索引)。

级联操作

选项含义
RESTRICT / NO ACTION默认。删除/更新父表记录时,如果子表有引用,则拒绝操作。
CASCADE删除/更新父表记录时,自动删除/更新子表的关联记录。
SET NULL删除/更新父表记录时,将子表的外键设为 NULL(要求外键列允许 NULL)。
SET DEFAULT设为默认值(InnoDB 不支持)。

场景一:RESTRICT(默认,保护数据)

当前数据状态:重新创建带 RESTRICT 的表

CREATE TABLE employees_restrict (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept_id INT,
    score DECIMAL(5,2),
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
        ON DELETE RESTRICT
        ON UPDATE RESTRICT
);

INSERT INTO employees_restrict (emp_name, dept_id, score) VALUES
('大翔', 1, 100),
('白歌', 2, NULL);

SELECT * FROM employees_restrict;
emp_idemp_namedept_idscore
1大翔1100
2白歌2NULL

操作语句:

-- 尝试删除有员工引用的部门
DELETE FROM departments WHERE dept_id = 1;

操作后数据状态:

ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails
SELECT * FROM departments;
dept_iddept_name
1技术部
2产品部

结果解读:ON DELETE RESTRICT 阻止了删除操作,因为 employees_restrict 表中还有 dept_id=1 的记录。这保护了数据,防止误删导致引用断裂。

场景二:CASCADE(级联删除)

当前数据状态:创建带 CASCADE 的表

CREATE TABLE employees_cascade (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept_id INT,
    score DECIMAL(5,2),
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
        ON DELETE CASCADE
        ON UPDATE CASCADE
);

INSERT INTO employees_cascade (emp_name, dept_id, score) VALUES
('大翔', 1, 100),
('白歌', 1, NULL);

SELECT * FROM employees_cascade;
emp_idemp_namedept_idscore
1大翔1100
2白歌1NULL

操作语句:

-- 删除技术部(dept_id = 1)
DELETE FROM departments WHERE dept_id = 1;

SELECT * FROM departments;

操作后数据状态:

dept_iddept_name
2产品部
SELECT * FROM employees_cascade;
emp_idemp_namedept_idscore

结果解读:ON DELETE CASCADE 使得删除 departments 表中 dept_id=1 的记录时,employees_cascade 表中所有 dept_id=1 的记录(大翔和白歌)被自动删除。子表已无任何数据。

⚠️ 危险警告:ON DELETE CASCADE 在父表删除时会自动级联删除子表数据。如果级联链很长,一次删除可能触发大量级联操作,造成"删一条丢一片"的事故。

场景三:SET NULL

当前数据状态:重新插入技术部部门,创建带 SET NULL 的表

INSERT INTO departments (dept_id, dept_name) VALUES (1, '技术部');

CREATE TABLE employees_setnull (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept_id INT,  -- 必须允许 NULL
    score DECIMAL(5,2),
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
        ON DELETE SET NULL
        ON UPDATE SET NULL
);

INSERT INTO employees_setnull (emp_name, dept_id, score) VALUES
('大翔', 1, 100),
('白歌', 1, NULL);

SELECT * FROM employees_setnull;
emp_idemp_namedept_idscore
1大翔1100
2白歌1NULL

操作语句:

-- 删除技术部
DELETE FROM departments WHERE dept_id = 1;

SELECT * FROM employees_setnull;

操作后数据状态:

emp_idemp_namedept_idscore
1大翔NULL100
2白歌NULLNULL

结果解读:ON DELETE SET NULL 使得删除技术部后,原本属于该部门的员工(大翔和白歌)的 dept_id 被自动设为 NULL,表示"未分配部门",而不是被删除。

场景四:ON UPDATE CASCADE

当前数据状态:employees_cascade 表当前无数据,重新准备数据

INSERT INTO departments (dept_id, dept_name) VALUES (1, '技术部');

INSERT INTO employees_cascade (emp_name, dept_id, score) VALUES
('大翔', 1, 100),
('白歌', 2, NULL);

SELECT * FROM employees_cascade;
emp_idemp_namedept_idscore
1大翔1100
2白歌2NULL

操作语句:

-- 修改产品部的 dept_id
UPDATE departments SET dept_id = 20 WHERE dept_id = 2;

SELECT * FROM departments;

操作后数据状态:

dept_iddept_name
1技术部
20产品部
SELECT * FROM employees_cascade;
emp_idemp_namedept_idscore
1大翔1100
2白歌20NULL

结果解读:ON UPDATE CASCADE 使得父表的 dept_id 从 2 改为 20 时,子表中对应的 dept_id 也自动同步为 20。这保证了引用关系的一致性。

外键的代价

  • 插入子表时:需要查询父表确认引用有效,增加一次查找
  • 删除父表时:需要检查子表是否有引用,可能锁定子表
  • 高并发场景:外键检查会增加锁竞争
  • 分库分表:跨库无法建立外键约束

常见误区

误区正解
外键会降低性能所以不要用外键确实有开销,但在单机 MySQL 中,它保证数据一致性的收益通常大于性能开销。高并发或分库场景可在应用层保证一致性。
外键只能在应用层替代应用层无法完全替代外键的原子性保障(竞态条件窗口)。单机场景推荐外键。
所有关联表都必须建外键视场景而定。日志表、临时表、高写入表可考虑不加外键。

面试考点

Q:外键的优缺点?

优点:保证引用完整性,支持级联操作,减少应用层代码。缺点:增加写入开销,删除父表时需检查子表,高并发下增加锁竞争,分库分表无法使用。

Q:ON DELETE CASCADE 有什么风险?

级联删除可能导致"删一条丢一片"的连锁反应。如果表之间的级联关系复杂,一次误删可能删除大量关联数据。生产环境使用级联删除需谨慎。

Q:MySQL 外键有什么限制?

  1. 父表和子表必须使用相同的存储引擎(InnoDB);2. 外键列和引用列必须有相同的数据类型和长度;3. 外键列必须有索引(MySQL 会自动创建);4. 不支持跨数据库的外键。

小结

  • 外键保证表与表之间的引用完整性
  • ON DELETE RESTRICT 保护数据,CASCADE 级联操作,SET NULL 置空
  • 外键有性能开销,高并发和分库场景可在应用层处理一致性
  • 使用 CASCADE 需谨慎,避免误删导致连锁反应

下一章引子:外键约束底层数据,视图则提供一个灵活的数据呈现层——它像一张虚拟的表。

上一页
修改视图与检查选项
下一页
索引