首页 / MySQL 入门教程 / 外键约束与级联操作

MySQL 入门教程

外键约束与级联操作

本教程共 46 篇 · 第 32 篇 · 更新于 2026-07-30 · 约 11 分钟阅读

MySQLMySQL 入门教程外键FOREIGN KEY级联CASCADE参照完整性约束

32. 外键约束与级联操作

本节目标:理解外键(Foreign Key)的作用和创建方法,掌握 CASCADE、SET NULL、RESTRICT 三种级联动作的区别,学会禁用外键检查来批量导入数据。

32.1 外键是什么

外键(Foreign Key) 是一张表中的某个列,它的值必须引用另一张表的主键或唯一键。简单说,外键就是把两张表”绑”在一起,保证数据的一致性。

usersorders 为例:每个订单属于一个用户,orders 表的 user_id 就是外键,它引用 users 表的 id

-- 先建父表(被引用的表)
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    age INT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 再建子表(带外键的表)
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Note

外键约束只在 InnoDB 引擎中生效。MyISAM 引擎虽然能写外键语法,但不强制执行。实际项目中基本都用 InnoDB。

32.2 外键的参照完整性

外键约束在两个方向上保护数据:

插入限制:子表的外键值必须在父表中存在。

-- users 表里没有 id=99 的用户
INSERT INTO orders(user_id, amount) VALUES(99, 100.00);
-- ERROR 1452 (23000): Cannot add or update a child row:
-- a foreign key constraint fails

删除限制:父表中被引用的行不能随便删。

-- 先正常插入数据
INSERT INTO users(username, email) VALUES('zhangsan', 'zhangsan@test.com');
INSERT INTO orders(user_id, amount) VALUES(1, 100.00);

-- 尝试删除用户,但订单还引用着这个用户
DELETE FROM users WHERE id = 1;
-- ERROR 1451 (23000): Cannot delete or update a parent row:
-- a foreign key constraint fails

这就是参照完整性(Referential Integrity):有订单引用的用户不能直接删,得先处理订单。

32.3 级联操作:ON DELETE / ON UPDATE

如果每次删除用户前都要手动删订单,太麻烦。MySQL 提供了级联操作,让数据库自动处理。

语法是在外键定义后加 ON DELETEON UPDATE 子句:

FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE 动作
    ON UPDATE 动作

动作有四种:

动作含义删父行时子表的行为
RESTRICT(默认)拒绝操作报错,不让删
CASCADE级联操作子表关联行一起删
SET NULL设为空子表外键列设为 NULL
NO ACTION等同 RESTRICT报错,不让删

CASCADE:连锁删除/更新

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
        ON DELETE CASCADE
        ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

设置了 ON DELETE CASCADE 后:

-- 删除用户,关联的订单自动跟着删
DELETE FROM users WHERE id = 1;
-- orders 表中 user_id=1 的行也被删了
Warning

CASCADE 用起来爽,但一定要想清楚。删一个用户,可能连锁删除几百条订单数据,误操作的话数据就没了。生产环境慎用 CASCADE,更推荐用逻辑删除(软删除)。

SET NULL:设为空

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
        ON DELETE SET NULL
        ON UPDATE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

删除用户后,订单不删,只是 user_id 变成 NULL:

DELETE FROM users WHERE id = 1;
-- orders 表中原来 user_id=1 的行,user_id 变成 NULL,订单数据还在
Note

用 SET NULL 的前提是外键列允许为 NULL。如果 user_id 设了 NOT NULL,SET NULL 就不生效。

32.4 给外键起名字

默认外键名字是 MySQL 自动生成的(类似 orders_ibfk_1),不好记也不好运维。建议起个有意义的名字:

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_orders_user_id
        FOREIGN KEY (user_id) REFERENCES users(id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CONSTRAINT 约束名 FOREIGN KEY ... 给外键起名为 fk_orders_user_id,一看就知道是 orders 表引用 users 的外键。

32.5 查看外键信息

查看表的外键:

SHOW CREATE TABLE orders\G

输出里会看到 CONSTRAINT fk_orders_user_id FOREIGN KEY (user_id) REFERENCES ...

也可以查 information_schema

SELECT
    CONSTRAINT_NAME,
    COLUMN_NAME,
    REFERENCED_TABLE_NAME,
    REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_NAME = 'orders' AND TABLE_SCHEMA = '你的库名';

32.6 删除外键

不需要某个外键约束时,可以删掉:

ALTER TABLE orders DROP FOREIGN KEY fk_orders_user_id;
Warning

删除外键要指定外键约束名,不是列名。DROP FOREIGN KEY user_id 是错的,要写 DROP FOREIGN KEY fk_orders_user_id

32.7 建表后添加外键

表已经存在时,用 ALTER TABLE 加外键:

ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE RESTRICT
    ON UPDATE CASCADE;

前提是被引用的列必须有索引(主键和唯一键自带索引,所以引用主键没问题)。

32.8 禁用和恢复外键检查

批量导入数据时,外键检查会很慢,而且导入顺序搞错就报错。可以临时关掉外键检查:

-- 关闭外键检查
SET foreign_key_checks = 0;

-- 这时可以先导入 orders,再导入 users,不会报错
INSERT INTO orders(user_id, amount) VALUES(999, 100.00);

-- 导完数据后恢复
SET foreign_key_checks = 1;
Tip

用 mysqldump 导出的 SQL 文件,头部通常就有 SET foreign_key_checks = 0,尾部有 SET foreign_key_checks = 1,就是为了保证导入不受外键顺序限制。

Warning

关闭外键检查后导入的数据,外键约束不会自动验证。恢复检查后如果数据不一致,不会报错,但数据已经”脏”了。务必确保导入的数据是正确的。

32.9 自引用外键

一张表可以引用自己的主键,叫自引用外键。典型场景是分类树、员工-经理关系:

CREATE TABLE categories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    parent_id INT,
    FOREIGN KEY (parent_id) REFERENCES categories(id)
        ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO categories(name) VALUES('电子产品');           -- id=1, parent_id=NULL
INSERT INTO categories(name, parent_id) VALUES('手机', 1);  -- id=2, parent_id=1
INSERT INTO categories(name, parent_id) VALUES('电脑', 1);  -- id=3, parent_id=1

删掉”电子产品”分类,子分类的 parent_id 会变成 NULL(因为设了 ON DELETE SET NULL)。

32.10 外键的代价

外键不是越多越好,它有代价:

  • 写入性能:每次 INSERT/UPDATE/DELETE 都要检查约束,有额外开销;
  • 锁竞争:外键检查会加锁,高并发下可能成为瓶颈;
  • 维护复杂:级联删除的连锁效应有时难以预测。
Tip

很多互联网公司在应用层做数据一致性校验,不在数据库里建外键。这样更灵活,也减少数据库压力。但如果是传统企业系统、数据一致性要求极高的场景,外键约束仍然是最可靠的选择。

32.11 小结

  • 外键保证两张表之间的参照完整性,子表值必须在父表中存在;
  • 级联动作四种:RESTRICT(默认拒绝)、CASCADE(连锁删除)、SET NULL(设为空)、NO ACTION;
  • 外键只在 InnoDB 中生效,建议起有意义的约束名;
  • SET foreign_key_checks = 0 可临时禁用外键检查,用于批量导入;
  • 自引用外键可用于树形结构(分类树、组织架构);
  • 外键有性能和维护代价,是否使用取决于业务场景。

下一节讲 UNIQUE 和 CHECK 约束,继续完善数据的完整性保护。