老马失前蹄,竟然在数据库外键上翻车了,重温外键级联


祸起 dev environment

某天晚上,群里突然有人@我,说开发环境出现报错,一查发现是我负责的接口出了问题,瞬间虎躯一震,赶紧定位问题。

谜底就在谜面上:主表数据被删除,但子表仍保留外键约束,导致删除操作直接失败

三分钟解决问题,行云流水,毫不拖泥带水。


反思与复盘

但这件事引发了我的反思:这种错误太低级了,低级到刚毕业的我都不可能犯,但工作多年后的我反而踩坑了。

复盘原因,主要有三点:

  1. 对外键本质理解不够深入

    人云亦云、浅尝辄止,没有真正吃透原理。

    温故而知新,希望未来不再被同样的问题"回旋镖"命中。

  2. 过度依赖 DBA 兜底,思考偷懒

    经验主义害人,任何技术点都不能"不带脑子"。

  3. 对业务上游逻辑不够熟悉

    不知道上游业务会执行删除操作。

    后续需要加强业务文档沉淀、多总结、多梳理。


外键 Foreign Key 解析

什么是外键?

数据库外键,是一个熟悉又陌生的词。

  • 熟悉:大学就学过,工作天天提
  • 陌生:商业项目里几乎从不真正启用

至于为什么不建外键?别问,问就是互联网公司传统!

当然,主要是没权限。

那为什么现在又开始建了?

一代版本一代神,换工作了 😂

外键(Foreign Key) 是一种数据约束 ,用于强制维护两张表之间的关联关系

通俗比喻

  • 主表 = 身份证表
  • 子表 = 学生表
  • 外键 = 规则约束:学生表中的身份证号,必须在身份证表中存在

核心作用:保证数据不乱、不丢、不错


外键能解决什么问题?

没有外键,很容易出现三类脏数据:

  1. 子表存在不存在于主表的引用值
  2. 主表数据被删,子表残留"孤儿数据"
  3. 关联查询逻辑混乱,需要频繁调整 JOIN 方式

有了外键:

  1. 数据库自动校验,无法非法插入/删除
  2. 关联关系永远合法
  3. 支持自动级联操作

外键常用 SQL 语法

1. 建表时直接添加外键

sql 复制代码
CREATE TABLE 子表 (
    字段名 数据类型,
    -- 外键定义
    CONSTRAINT 外键名称 
    FOREIGN KEY (子表字段) 
    REFERENCES 主表(主表字段)
    -- 可选:级联规则
    ON DELETE 行为
    ON UPDATE 行为
);

2. 给已有表添加外键

sql 复制代码
ALTER TABLE 子表
ADD CONSTRAINT 外键名称
FOREIGN KEY (子表字段)
REFERENCES 主表(主表字段)
ON DELETE 行为
ON UPDATE 行为;

3. 删除外键

sql 复制代码
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;

4. 修改外键(MySQL 不支持直接修改,需先删再加)

其他数据库如 SQL Server:

sql 复制代码
ALTER TABLE 表
ALTER CONSTRAINT 外键名
ON DELETE CASCADE;

⚠️ 最重要:外键的四种级联行为

外键的灵魂 就是:删除/更新主表时,子表应该如何自动处理

1. RESTRICT(默认行为,最安全)

  • 如果子表存在引用,直接报错,阻止操作
  • 不允许删除/更新主表记录

2. CASCADE(级联,最危险)

  • 主表删除 → 子表自动删除
  • 主表更新 ID → 子表自动同步
  • 一旦误操作,极易批量丢失数据

3. SET NULL

  • 主表删/改 → 子表对应字段自动设为 NULL
  • 要求子表字段允许为 NULL

4. SET DEFAULT(极少使用)

  • 与 SET NULL 类似,只是设为预设默认值

企业最常用的安全组合

sql 复制代码
ON DELETE RESTRICT  -- 不允许删除
ON UPDATE CASCADE   -- 允许改 ID,自动同步

为什么大多数互联网公司不使用外键?

外键优点

  1. 数据强一致性,数据库底层保证数据合法性
  2. 无需在代码中手动校验关联关系,简化逻辑
  3. 表关系清晰,便于生成 E-R 图、维护表结构

外键缺点(核心原因)

  1. 降低写入性能

    插入/删除都需要额外校验,高并发场景下影响明显。

  2. 隐式加锁,易导致锁等待、死锁

    例如插入子表时,数据库会自动查询并锁定主表对应记录,提高死锁概率。

  3. 不支持分库分表

    外键是数据库引擎内部功能,只能在同一个库、同一个实例 内生效。

    分布式架构下完全无法使用。

  4. 级联删除风险极高

    误操作难以恢复,不符合互联网业务"可容错、可兜底"的需求。


总结一句话

互联网企业不是不会用外键,而是不敢用、不能用、不需要用。

业务更需要:逻辑删除、数据迁移、临时异常容忍、灵活清洁数据。
如果你还是听不懂,那说人话就是,我们言必谈高并发,分布式,高可用,只有你坚持古法单数据库,强一致性,活该你背锅。


常见问题

1. 外键名称必须全库唯一吗?

是的,外键名称不能重复。

2. 子表已有数据,还能加外键吗?

只要现有数据完全合法(都能在主表找到对应记录),就可以添加;否则会直接失败。

3. 外键引用的字段必须是主键吗?

不需要主键,但必须具备唯一性

唯一键(UNIQUE)也可以被外键引用。

4. 为什么外键容易产生锁等待,死锁

举个例子,当你执行这条SQL时,因为外键约束的存在,会偷偷帮你做这两件事情,

复制代码
INSERT INTO 子表 (ProposalId) VALUES (100)

1. 主动去查一下主表,看看id=100存在吗?  -- 隐式加锁
2. 锁定主表的那条记录,防止在你插入的过程中,主表数据被删掉 -- 加S锁

因为锁的存在,极大的提高了等待,死锁的可能性。

5. 为什么不支持分表分库

外键能生效,是因为数据库引擎能直接在内部执行检查,加锁,校验等功能,它只能管好自己

而互联网的架构往往是分布式,身份证表可能被分为N个表,N个库。它们甚至不在同一个库,同一个机器上。

那么问题来了,身份证表在数据库A,学生表在数据库B,如何建立外键关系? 别忘了,数据库只能管好自己。更何况跨服务器的情况,那更加鞭长莫及了。