外键是InnoDB强制的引用检查机制,要求字段类型严格兼容、显式建索引,支持ON DELETE/UPDATE行为控制,非银弹,需权衡一致性、性能与架构演进。

外键本质是强制的“引用检查”
外键不是语法糖,也不是可有可无的装饰——它是 MySQL(仅 InnoDB)对“某条记录是否真的存在另一张表里”做的实时校验。比如 orders.user_id 设为外键指向 users.id,那插入 orders 时若填了个不存在的 user_id,MySQL 直接报错:Cannot add or update a child row: a foreign key constraint fails。这不是靠程序逻辑兜底,而是数据库层的硬性拦截。
- 它只在
InnoDB表生效,MyISAM完全无视外键定义(即使你写了也不会报错,但也不起作用) - 外键列必须显式建索引(
ALTER TABLE orders ADD INDEX idx_user_id (user_id)),否则建外键会失败 —— 即使字段名和主键一样,也不会自动索引 - 两边字段类型必须严格兼容:比如
INT和TINYINT UNSIGNED不行,BIGINT和INT也不行;字符集、排序规则也得一致
ON DELETE / ON UPDATE 不是可选项,是行为开关
外键不加 ON DELETE 或 ON UPDATE,就等于只开了“禁止非法插入”,但没管“父记录变了怎么办”。常见取值有:
-
RESTRICT(默认):删/改父记录前,先查子表有没有引用,有就直接拒绝 -
CASCADE:父删,子自动删;父改主键值,子外键值跟着改(慎用!尤其改主键在生产环境几乎从不发生) -
SET NULL:要求外键列允许NULL;父删/改后,子表对应外键字段设为NULL -
NO ACTION:和RESTRICT在 MySQL 中行为一致,语义上更偏向“由应用决定”,但实际仍是拒绝
举个真实场景:用户注销时想保留订单历史但断开归属,应设 ON DELETE SET NULL;而删除产品时连带清空库存记录,才用 CASCADE。别图省事全写 CASCADE,一个误删可能级联干掉几十张表的数据。
外键不是银弹,它和性能、迁移、ORM 都有摩擦
启用外键意味着每次 INSERT/UPDATE/DELETE 都要多一次关联表的索引查找和锁检查。高并发写入场景下,外键约束可能成为瓶颈,尤其是跨分片或大表 JOIN 的外键。
- 数据迁移或导入时,常因外键约束失败:先关约束
SET FOREIGN_KEY_CHECKS = 0,导入完再开(但务必确认数据逻辑自洽) - 很多 ORM(如 Django、Laravel Eloquent)默认不依赖外键做关系维护,而是靠代码层 join 和验证;这时外键反而成了部署负担,容易被忽略或漏建
- 分库分表、读写分离架构下,外键跨物理库根本不可用,此时必须退回到应用层一致性保障
ALTER TABLE orders ADD CONSTRAINT fk_orders_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL ON UPDATE RESTRICT;
外键和主键、索引的关系常被混淆
主键一定是唯一 + 非空 + 自动建聚簇索引;外键只是普通字段,它本身不保证唯一、不强制非空(除非你额外加 NOT NULL),且必须手动建索引。很多人以为“加了外键就自动索引了”,结果上线后 JOIN 慢得离谱,explain 一看 type: ALL —— 就是因为忘了给外键列加索引。
- 一对多关系中,外键在“多”的那张表上(如
orders.user_id) - 一对一关系中,外键可放任一边,但通常放在“附属表”上,并加
UNIQUE约束 - 多对多必须拆成三张表,中间关联表的两个字段分别作为外键,各自索引










