外键约束与级联操作
本教程共 46 篇 · 第 32 篇 · 更新于 2026-07-30 · 约 11 分钟阅读
32. 外键约束与级联操作
本节目标:理解外键(Foreign Key)的作用和创建方法,掌握 CASCADE、SET NULL、RESTRICT 三种级联动作的区别,学会禁用外键检查来批量导入数据。
32.1 外键是什么
外键(Foreign Key) 是一张表中的某个列,它的值必须引用另一张表的主键或唯一键。简单说,外键就是把两张表”绑”在一起,保证数据的一致性。
以 users 和 orders 为例:每个订单属于一个用户,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 DELETE 和 ON 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 的行也被删了
WarningCASCADE 用起来爽,但一定要想清楚。删一个用户,可能连锁删除几百条订单数据,误操作的话数据就没了。生产环境慎用 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 约束,继续完善数据的完整性保护。