外键约束仅校验单条语句的引用完整性,不保证事务一致性;其检查发生在语句执行时,依赖显式声明的ON DELETE/UPDATE行为,且要求外键列必须建索引以避免性能问题。

外键约束本身不保证事务一致性
外键约束(FOREIGN KEY)只负责在单条语句执行时校验引用完整性,比如 INSERT 或 UPDATE 时检查被引用的 id 是否存在。它不参与事务的原子性控制——即使你在一个事务里先删父记录、再删子记录,外键会立刻报错 Cannot delete or update a parent row: a foreign key constraint fails,但这不是事务在“保证一致性”,而是约束在“阻止不一致操作”。
真正靠事务来兜底的是 ROLLBACK 机制:只要你在事务中执行多步操作,其中任意一步因外键失败或其它原因中断,整个事务可回滚。
ON DELETE / ON UPDATE 的行为必须显式声明
MySQL 默认不会自动级联删除或更新,哪怕加了外键,也要明确写 ON DELETE CASCADE 或 ON DELETE SET NULL 等子句,否则默认是 RESTRICT(即拒绝操作)。
-
ON DELETE CASCADE:删父表行时自动删所有子表关联行 —— 快但危险,容易误删 -
ON DELETE SET NULL:要求对应列为NULLABLE,否则建表失败 -
ON DELETE RESTRICT:隐式默认值,不写也会生效,但建议显式写出,避免误解 - 注意:
SET DEFAULT在 InnoDB 中不支持,会静默转为RESTRICT
事务隔离级别对外键检查无影响
外键约束检查发生在语句执行阶段(statement-level),而不是事务提交时。无论你用 READ COMMITTED 还是 REPEATABLE READ,只要语句执行时被引用行不可见(比如被另一个未提交事务锁住或已删除),就会立即报错,不会等到 COMMIT 才检查。
这意味着:
- 外键检查不依赖 MVCC 版本可见性规则
- 如果子表插入依赖一个尚未提交的父表插入,会直接报
Cannot add or update a child row - 想绕过这个限制,只能提前提交父表事务,或改用应用层校验 + 延迟约束(MySQL 不支持延迟约束,PostgreSQL 才支持)
外键与性能、锁行为密切相关
加了外键后,MySQL(InnoDB)会在相关操作中自动加锁,范围比你直觉中更大:
- 插入子表时,会对父表被引用的那行加
shared lock(S 锁),防止父行被删/改 - 删除父表行时,会先对子表扫描匹配行(即使有索引),并加
next-key lock,可能阻塞并发插入 - 没给子表外键列建索引?会触发全表扫描 + 表级锁,高并发下极易卡死
- 建议:所有外键列必须单独建索引(
INDEX),否则 DML 性能断崖下跌
ALTER TABLE orders ADD INDEX idx_customer_id (customer_id);外键不是银弹。它把一部分数据逻辑推给了存储引擎,省了应用层判断,但也带来了锁行为不可控、迁移困难、分库分表难适配等问题。真要强一致性,得靠事务 + 应用层重试 + 补偿逻辑,而不是指望外键自己扛住所有边界。










