技术文章

数据库外键翻车复盘:重温外键级联

从一次删除失败事故出发,重新理解数据库外键、约束与级联操作的实际意义。

veg blog iOS 26 UI 技术内容

祸起 dev environment

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

image

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

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


反思与复盘

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

复盘原因,主要有三点:

  1. 对外键本质理解不够深入
    人云亦云、浅尝辄止,没有真正吃透原理。
    温故而知新,希望未来不再被同样的问题“回旋镖”命中。

  2. 过度依赖 DBA 兜底,思考偷懒
    经验主义害人,任何技术点都不能“不带脑子”。

  3. 对业务上游逻辑不够熟悉
    不知道上游业务会执行删除操作。
    后续需要加强业务文档沉淀、多总结、多梳理。


外键 Foreign Key 解析

什么是外键?

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

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

至于为什么不建外键?别问,问就是互联网公司传统!
当然,主要是没权限。
那为什么现在又开始建了?
一代版本一代神,换工作了 😂

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

通俗比喻

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

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


外键能解决什么问题?

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

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

有了外键:

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

外键常用 SQL 语法

1. 建表时直接添加外键

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

2. 给已有表添加外键

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

3. 删除外键

            
              ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;
            
          

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

其他数据库如 SQL Server:

            
              ALTER TABLE 表 ALTER CONSTRAINT 外键名 ON DELETE CASCADE;
            
          

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

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

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

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

2. CASCADE(级联,最危险)

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

3. SET NULL

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

4. SET DEFAULT(极少使用)

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

image


企业最常用的安全组合

            
              ON DELETE RESTRICT -- 不允许删除 ON UPDATE CASCADE -- 允许改 ID,自动同步
            
          

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

外键优点

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

外键缺点(核心原因)

  1. 降低写入性能
    插入/删除都需要额外校验,高并发场景下影响明显。

  2. 隐式加锁,易导致锁等待、死锁
    例如插入子表时,数据库会自动查询并锁定主表对应记录,提高死锁概率。

  3. 不支持分库分表
    外键是数据库引擎内部功能,只能在 同一个库、同一个实例 内生效。
    分布式架构下完全无法使用。

  4. 级联删除风险极高
    误操作难以恢复,不符合互联网业务“可容错、可兜底”的需求。


总结一句话

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


常见问题

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

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

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

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

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

不需要主键,但 必须具备唯一性
唯一键(UNIQUE)也可以被外键引用。

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

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

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

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

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

外键能生效,是因为数据库引擎能 直接在内部执行 检查,加锁,校验等功能,它 只能管好自己
而互联网的架构往往是分布式,身份证表可能被分为N个表,N个库。它们甚至不在同一个库,同一个机器上。
那么问题来了,身份证表在数据库A,学生表在数据库B,如何建立外键关系? 别忘了,数据库只能管好自己。更何况跨服务器的情况,那更加鞭长莫及了。