祸起 dev environment
某天晚上,群里突然有人@我,说开发环境出现报错,一查发现是我负责的接口出了问题,瞬间虎躯一震,赶紧定位问题。
谜底就在谜面上:主表数据被删除,但子表仍保留外键约束,导致删除操作直接失败。
三分钟解决问题,行云流水,毫不拖泥带水。
反思与复盘
但这件事引发了我的反思:这种错误太低级了,低级到刚毕业的我都不可能犯,但工作多年后的我反而踩坑了。
复盘原因,主要有三点:
-
对外键本质理解不够深入
人云亦云、浅尝辄止,没有真正吃透原理。
温故而知新,希望未来不再被同样的问题"回旋镖"命中。
-
过度依赖 DBA 兜底,思考偷懒
经验主义害人,任何技术点都不能"不带脑子"。
-
对业务上游逻辑不够熟悉
不知道上游业务会执行删除操作。
后续需要加强业务文档沉淀、多总结、多梳理。
外键 Foreign Key 解析
什么是外键?
数据库外键,是一个熟悉又陌生的词。
- 熟悉:大学就学过,工作天天提
- 陌生:商业项目里几乎从不真正启用
至于为什么不建外键?别问,问就是互联网公司传统!
当然,主要是没权限。
那为什么现在又开始建了?
一代版本一代神,换工作了 😂
外键(Foreign Key) 是一种数据约束 ,用于强制维护两张表之间的关联关系。
通俗比喻
- 主表 = 身份证表
- 子表 = 学生表
- 外键 = 规则约束:学生表中的身份证号,必须在身份证表中存在
核心作用:保证数据不乱、不丢、不错。
外键能解决什么问题?
没有外键,很容易出现三类脏数据:
- 子表存在不存在于主表的引用值
- 主表数据被删,子表残留"孤儿数据"
- 关联查询逻辑混乱,需要频繁调整 JOIN 方式
有了外键:
- 数据库自动校验,无法非法插入/删除
- 关联关系永远合法
- 支持自动级联操作
外键常用 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,自动同步
为什么大多数互联网公司不使用外键?
外键优点
- 数据强一致性,数据库底层保证数据合法性
- 无需在代码中手动校验关联关系,简化逻辑
- 表关系清晰,便于生成 E-R 图、维护表结构
外键缺点(核心原因)
-
降低写入性能
插入/删除都需要额外校验,高并发场景下影响明显。
-
隐式加锁,易导致锁等待、死锁
例如插入子表时,数据库会自动查询并锁定主表对应记录,提高死锁概率。
-
不支持分库分表
外键是数据库引擎内部功能,只能在同一个库、同一个实例 内生效。
分布式架构下完全无法使用。
-
级联删除风险极高
误操作难以恢复,不符合互联网业务"可容错、可兜底"的需求。
总结一句话
互联网企业不是不会用外键,而是不敢用、不能用、不需要用。
业务更需要:逻辑删除、数据迁移、临时异常容忍、灵活清洁数据。
如果你还是听不懂,那说人话就是,我们言必谈高并发,分布式,高可用,只有你坚持古法单数据库,强一致性,活该你背锅。
常见问题
1. 外键名称必须全库唯一吗?
是的,外键名称不能重复。
2. 子表已有数据,还能加外键吗?
只要现有数据完全合法(都能在主表找到对应记录),就可以添加;否则会直接失败。
3. 外键引用的字段必须是主键吗?
不需要主键,但必须具备唯一性 。
唯一键(UNIQUE)也可以被外键引用。
4. 为什么外键容易产生锁等待,死锁
举个例子,当你执行这条SQL时,因为外键约束的存在,会偷偷帮你做这两件事情,
INSERT INTO 子表 (ProposalId) VALUES (100)
1. 主动去查一下主表,看看id=100存在吗? -- 隐式加锁
2. 锁定主表的那条记录,防止在你插入的过程中,主表数据被删掉 -- 加S锁
因为锁的存在,极大的提高了等待,死锁的可能性。
5. 为什么不支持分表分库
外键能生效,是因为数据库引擎能直接在内部执行检查,加锁,校验等功能,它只能管好自己。
而互联网的架构往往是分布式,身份证表可能被分为N个表,N个库。它们甚至不在同一个库,同一个机器上。
那么问题来了,身份证表在数据库A,学生表在数据库B,如何建立外键关系? 别忘了,数据库只能管好自己。更何况跨服务器的情况,那更加鞭长莫及了。