外键的 ON DELETE 与 ON UPDATE:父子表如何联动

外键不止是"约束",它在定义父表变动时子表该怎样活下去

零、这篇文章讲什么

上一篇把四种"键"讲清楚了,但外键留了个尾巴:ON DELETE SET NULL ON UPDATE CASCADE 这一串英文,当时我一眼扫过去直接跳过------结果面试被问"外键的三种行为区别"时当场懵住。这一篇就是来补这个窟窿的。

读完你会得到三样东西:

  1. 外键的完整语法模板,ON DELETE / ON UPDATE 到底在配置什么
  2. 一个核心认知:外键检查是双向的,不是只管着子表
  3. 三种行为(RESTRICT / CASCADE / SET NULL)何时用哪个

前置知识:读完系列第 01 篇,知道外键是"子表指向父表"的引用即可。

一、先看最绕的困惑

直觉上,"子跟着父"------子表的数据依赖父表存在,那外键应该是管着子表 的。但为什么我看到的解释里写着"父表被删时会被禁止"?到底是管父还是管子?

答案在一开始就埋着:外键的检查是双向的,只是两个方向各管一件事。 破题就在这一句话:

写入时管"子"(子必须认父),删除/更新父时管"父"(父必须对子负责)。

二、外键的完整语法模板

sql 复制代码
CONSTRAINT 约束名
  FOREIGN KEY (子表列)
  REFERENCES 父表 (父表列)
  [ON DELETE 行为] [ON UPDATE 行为]

四个部分逐个说:

  • CONSTRAINT 约束名 :给外键起个名字(comment_ibfk_1 里的 ibfk 是 MySQL 自动生成的命名惯例,= InnoDB Foreign Key)。起名是为了以后能 DROP FOREIGN KEY 约束名 单独删掉它。
  • FOREIGN KEY (userId)子表里的列。
  • REFERENCES user (id)父表的哪一列。两边数据类型必须一致。
  • ON DELETE / ON UPDATE :父表那行被删除 / 修改 时,子表怎么办。可单独写、可都写、可不写 (不写 = 默认 RESTRICT,禁止)。

三、双向检查到底怎么运作

方向一:写入时,限制的是"子"(这条永远存在)

往 comment 表插一条 userId=999,数据库先查 user 表------没有 999 这个用户,拒绝插入 。这是"子必须认父",限制的是子表。这个检查不写 ON DELETE 也永远存在,它是外键的底线。

方向二:删除/更新父时,限制的是"父"

ON DELETE / ON UPDATE 管的是这个方向:父表要动,先问"还有子引用我吗?"答案由你写的行为决定:

  • RESTRICT(不写 = 默认)→ 父不许动 :还有评论引用你,你想删这个用户?不行,除非先把他的评论删掉或改掉 userId
  • CASCADE → 父可以动,子跟着删:文章删了,它的所有评论跟着一起删。
  • SET NULL → 父可以动,子引用置空 :父评论删了,楼中楼的 parentId 置成 NULL。

所以"子跟着父"这个直觉没错------它描述的是数据上的从属 (子存在的前提是父存在)。而 ON DELETE 系列在回答另一个问题:父变动时,子怎么办。默认的答案是"父别动,直到子先脱离关系"。

一句话:子乱报父 → 插不进去(限制子);父想跑 → 被拦住或带上子一起处理(限制父)。

四、实战:读懂评论表的三条外键

这是我笔记里评论表的完整结构,三条外键正好演示三种行为:

sql 复制代码
CREATE TABLE `comment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` longtext,
  `postId` int(11) NOT NULL,
  `userId` int(11) NOT NULL,
  `parentId` int(11) DEFAULT NULL,          -- 评论的评论,树状
  PRIMARY KEY(`id`),
  CONSTRAINT `comment_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`),
  CONSTRAINT `comment_ibfk_2` FOREIGN KEY (`parentId`) REFERENCES `comment` (`id`)
    ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `comment_ibfk_3` FOREIGN KEY (`postId`) REFERENCES `post` (`id`)
    ON DELETE CASCADE ON UPDATE CASCADE
)

第一条:userId → user(默认 RESTRICT)

sql 复制代码
FOREIGN KEY (`userId`) REFERENCES `user` (`id`)

没写 ON DELETE,走默认的 RESTRICT。含义:有评论的用户删不掉,除非先把他的评论清掉。

为什么这里不写 CASCADE 也不写 SET NULL? 看表结构就明白:

  • 不能 SET NULLuserId int NOT NULL,置空直接报错
  • 不该 CASCADE:用户删了不该连带删掉他发过的所有评论(评论属于文章,得保留)

所以用默认的 RESTRICT:用户必须自己先清理。这是符合业务的设计------三条外键三种行为,不是随便写的,是跟着业务语义选的。

第二条:parentId → comment 自己(SET NULL + CASCADE)

sql 复制代码
FOREIGN KEY (`parentId`) REFERENCES `comment` (`id`)
  ON DELETE SET NULL ON UPDATE CASCADE

先看结构:comment 引用 comment 自己,这叫自引用外键 ------评论区"楼中楼"就是它:一条评论的 parentId 指向另一条评论的 idparentIdNULL 就是顶层评论。

两个行为:

  • ON DELETE SET NULL :删了一条评论,所有"回复它"的评论 parentId 被置空,自动升级成顶层评论------而不是被连带删掉。评论没做错什么,不该跟着父评论一起消失。
  • ON UPDATE CASCADE :评论的 id 被修改,引用它的 parentId 跟着改,引用不悬空。

注意 SET NULL 能成立的前提:parentId int DEFAULT NULL(可空)。能不能 SET NULL,取决于列是否可空------这条对照着第一条看特别清楚。

第三条:postId → post(CASCADE + CASCADE)

sql 复制代码
FOREIGN KEY (`postId`) REFERENCES `post` (`id`)
  ON DELETE CASCADE ON UPDATE CASCADE
  • ON DELETE CASCADE :文章被删,它的所有评论跟着一起删------文章都没了,评论留着没意义,所以级联删。
  • ON UPDATE CASCADEpost.id 改了,postId 跟着改(自增主键几乎不会改,但写上保证引用一致)。

三条放一起看的设计逻辑

外键 指向 删除行为 为什么
userId → user 用户 RESTRICT 评论要保留,用户需自己清理
parentId → comment 评论自己 SET NULL 楼中楼脱离父评论,独立存活
postId → post 文章 CASCADE 文章没了,评论没意义,一起删

外键的 ON DELETE / ON UPDATE 就是在用 SQL 声明"父子数据该怎么联动",选哪种看业务语义:评论跟着文章死(CASCADE)、评论脱离父评论独立存活(SET NULL)、还是禁止动父数据(RESTRICT)。

五、三种行为对比表(面试直接背)

行为 父行删除时 父行主键修改时
RESTRICT / NO ACTION(默认) 有子行引用就禁止删除 有子行引用就禁止修改
CASCADE 子行连带删除 子行引用跟着改
SET NULL 子行引用列置空(列必须可空) 子行引用列置空

记忆口诀:RESTRICT 是"父别跑,子还没着落";CASCADE 是"父走了,子跟着走";SET NULL 是"父走了,子放养"。

六、一个生活化的类比

把 user 当父亲,comment 当子女:

场景 数据库行为
子女上户口 必须报真实父亲(userId 必须存在于 user)→ 限制
父亲想销户(删父行) 默认 RESTRICT不许销 ------你还有子女挂名下,先处理子女再销 → 限制
ON DELETE CASCADE 可以销,子女跟着一起销
ON DELETE SET NULL 可以销,子女变成"无父"(parentId 置空)

七、题外话:为什么大厂业务里常常"故意不用外键"

学到这里你可能会想:外键这么好,为什么听说很多公司不用?

原因有三个:

  1. RESTRICT 会锁住父表删除------删一个用户前,数据库要先检查所有子表有没有引用,大表下这个检查很重,还容易死锁
  2. 跨库没法用外键------表一旦分到不同数据库(分布式),外键约束就失效了
  3. 外键把联动逻辑藏在数据库里 ,而团队更习惯把"删了文章连带删评论"写成应用层代码,逻辑看得见、可测试、可控

所以很多项目是"表结构照设计、外键不建,靠代码保证一致性"。你的学习项目里用外键把这些行为跑熟,完全没问题------学的是机制,用不用是另一个话题。

八、小结

  • 外键语法:CONSTRAINT 名 FOREIGN KEY(子列) REFERENCES 父表(父列) [ON DELETE] [ON UPDATE]
  • 外键检查双向:写入限制子(认父),删除/更新父限制父(负责)
  • 三种行为:RESTRICT 禁止父动 / CASCADE 子跟着走 / SET NULL 子引用置空
  • SET NULL 要求列可空,CASCADE 适合"父亡子随"的语义
  • 生产里常不用外键,把联动逻辑放应用层

一句话总结:外键不止是"约束",ON DELETE / ON UPDATE 是在定义父表变动时,子表数据该怎样联动。

相关推荐
ZGG0031 小时前
底座 01:Agent 的 token 成本怎么砍——Redis 缓存实战
数据库·redis·缓存
小小龙学IT1 小时前
Qt Graphics View Framework(图形视图框架)深度解析:从 Scene/View/Item 到工业级 2D 场景
开发语言·数据库·qt
aikopen1 小时前
高可用 API 网关限流与微服务过载保护实战:从 Sentinel 动态阈值到 DB 连接池舱壁
数据库·微服务·sentinel
whcyhhh1 小时前
头歌实践教学平台:数据科学与大数据技术导论(七下)
大数据·数据库·python
迪康coolmu1 小时前
企业IT运维闭环——工单管理与远程协助一体化实践
java·大数据·运维·网络·数据库·人工智能·安全
cmes_love1 小时前
期权分钟行情数据下载:商品/股指/ETF期权全覆盖
数据库·区块链
骇客野人2 小时前
MySQL / PostgreSQL 单表及表数据备份恢复实施方案
数据库·mysql·postgresql
东方护航数据恢复(深圳)2 小时前
勒索病毒零赎金解密:加密样本逆向分析与可恢复性评估方法论
大数据·网络·数据库
心平气和量大福大2 小时前
android-实例2-数据库sqlite(查询)
android·数据库·sqlite