mysql外键

简介

外键是 MySQL 中用来建立和强制两个表之间关联关系的约束,作用是:

  • 保证数据的参照完整性 :子表(从表)的外键字段值,必须在主表(父表)的主键 / 唯一键中存在,或者为 NULL
  • 实现表与表的关联 :比如 emp 员工表的 dept_id 关联 dept 部门表的 id,确保员工不会分配到一个不存在的部门。
  • 配合级联操作,可以实现主表数据更新 / 删除时,子表数据自动同步。

删除主表中的数据,必须保证存有外键的子表中的相关数据都要被删除之后才能删除主表中的数据,从而保证了数据的完整性。

核心概念

表格

术语 含义 对应我们的表
主表(父表) 被引用的表,包含主键 / 唯一键 dept 部门表(id 是主键)
子表(从表) 引用外键的表,包含外键字段 emp 员工表(dept_id 是外键)
外键字段 子表中用来关联主表的字段 emp.dept_id
外键名称 给这个约束起的名字,方便后续删除 / 修改 比如 fk_emp_dept

二、外键的完整语法

1. 两种添加外键的方式

方式 1:创建表时直接添加外键(CREATE TABLE)

sql

复制代码
CREATE TABLE 表名(
    字段名 数据类型,
    ...
    -- 外键约束语法
    [CONSTRAINT 外键名称] FOREIGN KEY (外键字段名) 
    REFERENCES 主表(主表列名)
    [ON DELETE 级联操作] [ON UPDATE 级联操作]
);
  • CONSTRAINT 外键名称:可选,建议手动命名,方便后续管理;不写则 MySQL 自动生成。
  • FOREIGN KEY (外键字段名):子表中作为外键的字段。
  • REFERENCES 主表(主表列名):关联的主表和主表的主键 / 唯一键。
  • ON DELETE/ON UPDATE:可选,级联操作规则(后面详细讲)。
方式 2:表已创建,后续添加外键(ALTER TABLE)

sql

复制代码
alter table emp3
add constraint fk_emp_dept
foreign key (dept_id)
references dept(id);
复制代码
ALTER TABLE 子表名 
ADD CONSTRAINT 外键名称 
FOREIGN KEY (外键字段名)   --子表中的字段
REFERENCES 主表(主表列名)    --父表中的字段
[ON DELETE 级联操作] [ON UPDATE 级联操作];
  • 适合表已经建好、数据已经插入后,再补加外键约束。

2. 删除外键的语法

sql

复制代码
alter table emp3 drop foreign key fk_emp3_dept;
复制代码
ALTER TABLE 子表名 DROP FOREIGN KEY 外键名称;
  • 必须通过外键名称删除,不能通过字段名删除,所以建议创建时手动命名。

注意事项

  • 存储引擎要求
    • 只有 InnoDB 引擎支持外键,MyISAM 不支持!创建表时默认是 InnoDB,无需手动指定。
  • 字段类型必须一致
    • 子表外键字段的数据类型、长度、字符集,必须和主表被引用的字段完全一致。
    • 比如主表 dept.idINT,子表 emp.dept_id 也必须是 INT,不能是 BIGINTVARCHAR
  • 主表必须有索引
    • 主表被引用的列必须是主键(PRIMARY KEY)唯一键(UNIQUE KEY),否则无法创建外键。
  • 外键字段可以为 NULL
    • 子表外键字段可以插入 NULL,表示该记录不关联主表(比如员工未分配部门)。
  • 插入数据顺序
    • 必须先插入主表数据,再插入子表数据,否则子表外键值在主表不存在,会报错。
    • 比如:必须先在 dept 插入部门,再在 emp 插入员工,否则 dept_id 无效。
  • 删除数据顺序
    • 必须先删除子表数据,再删除主表数据(默认 RESTRICT 规则下),否则会报错。
    • 比如:要删除 dept 中 id=1 的研发部,必须先删除 emp 中所有 dept_id=1 的员工,否则无法删除。
  • 外键会影响性能
    • 每次插入 / 更新子表数据,都会校验主表,会有性能开销;高并发、大数据量场景,很多公司会选择业务层实现关联,不使用数据库外键
  • MySQL 外键约束行为(ON DELETE / ON UPDATE)深度详解

外键的核心价值,除了强制参照完整性 ,更关键的是通过约束行为(级联规则),定义「主表数据发生变化时,子表该如何响应」,完美解决业务中的关联数据同步问题。

5 种核心约束行为(全场景覆盖)

MySQL 支持 5 种外键约束行为,用「部门 - 员工」的业务场景,讲透每一种的效果、适用场景和优缺点:

表格

约束行为 英文全称 核心逻辑 主表删除 / 更新时,子表的动作 适用场景 风险提示
RESTRICT Restrict 限制 / 阻止 直接拒绝主表的操作 ,报错 Cannot delete or update a parent row 防止误删 / 误改有子表关联的主表数据(默认行为) 无风险,最安全
NO ACTION No Action 无动作 RESTRICT 完全等价,MySQL 中两者效果 100% 一致 标准 SQL 写法,和 RESTRICT 二选一即可 RESTRICT
CASCADE Cascade 级联 主表删除 / 更新,子表同步执行相同操作 部门删除,对应员工自动删除;部门 ID 修改,员工部门 ID 同步更新 高危!误删主表会直接清空子表关联数据
SET NULL Set Null 置空 主表删除 / 更新,子表外键字段自动设为 NULL 部门删除,员工部门 ID 置空(员工保留,标记为「未分配部门」) 子表外键字段必须允许为 NULL,否则报错
SET DEFAULT Set Default 设默认值 主表删除 / 更新,子表外键字段自动设为默认值 部门删除,员工部门 ID 自动设为默认部门(比如总经办) MySQL 8.0 仅 InnoDB 引擎支持,且需子表外键字段有明确 DEFAULT 值,极少使用

1. 默认行为:RESTRICT / NO ACTION(最安全,零风险)

语法

sql

复制代码
-- 创建表时指定(默认可以省略,因为是默认行为)
CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(id)
ON DELETE RESTRICT ON UPDATE RESTRICT;
效果演示
  • 场景:dept 表有 id=1(研发部),emp 表有 dept_id=1 的员工(张三、李四)

  • 执行删除主表操作:DELETE FROM dept WHERE id = 1;

  • 结果 :直接报错,拒绝删除,提示:

    plaintext

    复制代码
    Cannot delete or update a parent row: a foreign key constraint fails (`test`.`emp`, CONSTRAINT `fk_emp_dept` FOREIGN KEY (`dept_id`) REFERENCES `dept`(`id`))
  • 核心逻辑:只要子表有对应数据,就绝对不允许主表删除 / 更新,从根源上避免「孤儿数据」(员工有部门 ID,但部门不存在)。

  • 适用场景:绝大多数业务系统,尤其是核心数据(比如订单 - 商品、用户 - 账户),防止误操作。

2. 级联操作:CASCADE(最常用,同步数据)

语法

sql

复制代码
-- 级联删除 + 级联更新
CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(id)
ON DELETE CASCADE ON UPDATE CASCADE;
效果演示
  • 场景 1:级联删除(ON DELETE CASCADE)
    • 主表 dept 删除 id=1(研发部)
    • 子表 emp 中所有 dept_id=1 的员工(张三、李四)自动被删除
  • 场景 2:级联更新(ON UPDATE CASCADE)
    • 主表 dept 更新 id=1id=10
    • 子表 emp 中所有 dept_id=1 的员工,dept_id 自动同步为 10
  • 核心逻辑:主表的操作「穿透」到子表,完全同步,无需手动维护。
  • 适用场景:强关联、从属关系的数据(比如订单 - 订单明细、文章 - 评论),主表删除,子表数据无保留价值。
  • ⚠️ 高危警告
    • 误删主表会直接清空子表所有关联数据,生产环境必须谨慎使用
    • 建议仅在「主表删除 = 子表数据彻底无用」的场景使用,比如「购物车 - 商品」不适合(删除商品不能删用户购物车)

3. 置空操作:SET NULL(保留数据,解除关联)

语法

sql

复制代码
-- 主表删除/更新时,子表dept_id设为NULL
CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(id)
ON DELETE SET NULL ON UPDATE SET NULL;
效果演示
  • 场景:dept 表删除 id=1(研发部)
  • 子表 emp 中所有 dept_id=1 的员工,dept_id 自动变为 NULL,员工数据保留
  • 核心逻辑:保留子表数据,仅解除与主表的关联,标记为「未关联」。
  • 适用场景
    • 部门解散,但员工需要保留(比如调岗、待分配)
    • 商品下架,但用户订单 / 购物车需要保留(外键设为 NULL,标记为「已下架商品」)
  • ⚠️ 前提条件
    • 子表外键字段(emp.dept_id必须允许为 NULL (不能加 NOT NULL 约束),否则创建外键直接报错。

4. 设默认值:SET DEFAULT(极少使用,兼容性差)

语法

sql

复制代码
-- 子表dept_id必须设置DEFAULT默认值,比如DEFAULT 5(总经办ID=5)
CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(id)
ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
效果演示
  • 场景:dept 表删除 id=1(研发部)
  • 子表 emp 中所有 dept_id=1 的员工,dept_id 自动设为默认值 5(总经办)
  • 核心逻辑:主表数据失效时,子表自动关联到默认主表数据。
  • 适用场景
    • 部门删除,员工自动归属到「总经办」
    • 商品删除,订单自动关联到「已下架商品」虚拟商品
  • ⚠️ 限制说明
    • MySQL 8.0 仅 InnoDB 引擎支持,旧版本 / 其他引擎不支持
    • 子表外键字段必须有明确的 DEFAULT 值,且该值必须在主表中存在(否则报错)
    • 实际生产中极少使用,兼容性和灵活性差,一般用 SET NULL + 业务逻辑替代

四、组合使用示例(真实业务最佳实践)

场景 1:部门 - 员工(最常用组合)

  • 需求:部门删除时,员工保留(部门 ID 置空);部门 ID 修改时,员工部门 ID 同步更新

sql

复制代码
CREATE TABLE emp(
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    dept_id INT COMMENT '部门ID(允许为NULL,未分配部门)',
    -- 组合规则:删除置空,更新级联
    CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(id)
    ON DELETE SET NULL  -- 部门删除,员工dept_id置空
    ON UPDATE CASCADE  -- 部门ID修改,员工dept_id同步更新
) COMMENT '员工表';

场景 2:订单 - 订单明细(强关联)

  • 需求:订单删除,明细自动删除;订单 ID 修改,明细同步更新

sql

复制代码
CREATE TABLE order_detail(
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_id INT NOT NULL COMMENT '订单ID',
    product_id INT NOT NULL COMMENT '商品ID',
    -- 级联删除+级联更新
    CONSTRAINT fk_detail_order FOREIGN KEY (order_id) REFERENCES `order`(id)
    ON DELETE CASCADE ON UPDATE CASCADE
) COMMENT '订单明细表';

场景 3:商品 - 库存(严格限制,防止误删)

  • 需求:只要有库存,就绝对不允许删除商品

sql

复制代码
CREATE TABLE stock(
    id INT AUTO_INCREMENT PRIMARY KEY,
    product_id INT NOT NULL COMMENT '商品ID',
    stock_num INT NOT NULL COMMENT '库存数量',
    -- 默认RESTRICT,拒绝删除/更新
    CONSTRAINT fk_stock_product FOREIGN KEY (product_id) REFERENCES product(id)
    ON DELETE RESTRICT ON UPDATE RESTRICT
) COMMENT '库存表';

1. 字段类型与约束要求

  • 子表外键字段的数据类型、长度、字符集,必须和主表被引用的主键 / 唯一键完全一致
  • SET NULL 要求子表外键字段允许为 NULLSET DEFAULT 要求子表外键字段有明确 DEFAULT
  • 主表被引用的列必须是主键(PRIMARY KEY)唯一键(UNIQUE KEY),否则无法创建外键

2. 存储引擎要求

  • 只有 InnoDB 引擎支持外键,MyISAM 不支持(创建外键不会报错,但不生效)
  • SET DEFAULT 仅 InnoDB 引擎支持,其他引擎不支持

3. 性能影响

  • 外键约束会在每次插入 / 更新 / 删除时,强制校验主表数据,会有一定性能开销
  • 高并发、大数据量的互联网系统(比如电商、社交),通常会关闭数据库外键,在业务层实现关联逻辑,以提升性能
  • 内部管理系统、数据一致性要求高的场景,推荐使用外键,减少业务代码复杂度

4. 数据插入 / 删除顺序

  • 插入顺序:必须先插入主表数据,再插入子表数据(否则子表外键值在主表不存在,报错)
  • 删除顺序
    • RESTRICT/NO ACTION:必须先删子表,再删主表
    • CASCADE:直接删主表,子表自动删除
    • SET NULL/SET DEFAULT:直接删主表,子表自动置空 / 设默认值

5. 外键删除

  • 删除外键必须通过外键名称 ,不能通过字段名删除,所以创建时建议手动命名(比如 fk_emp_dept
  • 语法:ALTER TABLEempDROP FOREIGN KEY fk_emp_dept;

七、外键约束行为速查表(直接保存)

表格

操作 RESTRICT/NO ACTION CASCADE SET NULL SET DEFAULT
主表删除 拒绝删除,报错 子表同步删除 子表外键设为 NULL 子表外键设为默认值
主表更新 拒绝更新,报错 子表同步更新 子表外键设为 NULL 子表外键设为默认值
安全性 最高 最低(高危)
适用场景 核心数据、防误删 强关联从属数据 保留数据、解除关联 自动归属默认数据
相关推荐
2301_781571421 天前
Golang格式化输出占位符都有什么_Golang fmt占位符教程【通俗】
jvm·数据库·python
养肥胖虎1 天前
RAG学习笔记(3):区分数据库检索与RAG的使用场景
数据库·ai·rag
_ku_ku_1 天前
数据库系统原理 · 数据库应用开发 · 自学总结
数据库
长谷深风1111 天前
索引提速秘籍【个人八股】
mysql·b+树·索引·索引设计原则·存储引擎优化·索引维护成本·字段选择策略
No8g攻城狮1 天前
【人大金仓】wsl2+ubuntu22.04安装人大金仓数据库V9
java·数据库·spring boot·非关系型数据库
山峰哥1 天前
SQL慢查询调优实战:从全表扫描到索引覆盖的完整复盘
前端·数据库·sql·性能优化
Irene19911 天前
在 WSL 中下载安装 MySQL,连接到 SQLyog(MySQL 安装在 WSL vs Windows 本地对比)
mysql·wsl
代码中介商1 天前
Redis入门:5大数据类型全解析
数据库·redis·缓存
渣渣盟1 天前
数据库设计范式详解(纯小白版)
数据库·oracle·软考·数据库工程师
夜雪闻竹1 天前
Cursor 对话导入:解析 SQLite 里的宝藏
数据库·sqlite·ai编程