外键约束本身不慢,慢的是缺索引、失控级联和未避开检查时机;90%删除慢因外键列或父表引用列无索引,10%由ON DELETE CASCADE引发连锁操作导致锁表与日志膨胀。

外键约束本身不慢,慢的是没索引、没控制好级联、没避开检查时机——删得慢,90%是因为外键列或父表引用列缺索引,剩下10%是被 ON DELETE CASCADE 拖垮的。
外键列和父表引用列必须手动建索引
MySQL 不会自动为外键列建索引,哪怕你定义了 FOREIGN KEY (user_id) REFERENCES users(id),user_id 列依然可能没索引。一旦执行 DELETE FROM users WHERE id = 123,InnoDB 就得在子表里全表扫描找所有 user_id = 123 的行。
- 用
SHOW CREATE TABLE orders确认user_id是否已有索引;没有就立刻加:ALTER TABLE orders ADD INDEX idx_user_id (user_id) - 父表的被引用列(如
users.id)也必须是PRIMARY KEY或带UNIQUE索引,否则子表外键操作会变慢 - 如果查询常带其他条件,考虑把外键列作为复合索引最左前缀,比如
INDEX idx_user_status (user_id, status)
慎用 ON DELETE CASCADE,尤其数据量大时
ON DELETE CASCADE 看似省事,实际会把一次删除变成 N 次隐式 DML:删父记录 → 扫子表匹配行 → 删子行 → 再触发子表的外键检查……链式反应容易锁表、撑爆 undo log、拉高主从延迟。
- 线上环境建议禁用
CASCADE,改由应用层分批处理:先查出要删的子记录 ID 列表,再用DELETE FROM orders WHERE id IN (...)分批删 - 若必须保留级联,确保子表外键列有高效索引,且单次级联影响行数可控(
EXPLAIN验证子表扫描行数) - 避免多层级联(A→B→C),这种结构在大表上极易引发雪崩式锁等待
批量删除前临时关闭外键检查
当你确定删除逻辑不会破坏参照完整性(比如只删归档数据、子表已清空),可以临时跳过外键校验,节省每次 DML 的额外查表开销。
- 执行前加:
SET FOREIGN_KEY_CHECKS = 0;删完立刻恢复:SET FOREIGN_KEY_CHECKS = 1 - 务必在同一个会话内完成,不要跨连接操作;恢复语句不能遗漏,否则后续插入可能静默失败
- 该操作对当前会话生效,不影响其他连接,但需确认无其他并发写入依赖此约束
- 不适用于含
CASCADE的场景——关了检查,级联也不会触发
别忽略分区表这个“外键友好型”替代方案
如果删除条件天然按时间/租户划分(如日志表按 create_time 分区),直接用 DROP PARTITION 比任何带外键的 DELETE 都快,且完全绕开外键检查、索引更新、undo 日志。
- 前提:表已用
RANGE或LIST按外键无关字段(如时间)分区,且分区键上有索引 - 执行:
ALTER TABLE logs DROP PARTITION p_202506,秒级完成,不锁表、不写 binlog 大量事件 - 限制:不能用于被其他表外键引用的表(MySQL 不允许对有外键依赖的表做分区 DDL),所以得提前规划表结构
真正容易被忽略的点是:即使你给外键列加了索引,如果父表被引用列只是普通索引而非 PRIMARY KEY 或 UNIQUE,InnoDB 仍可能在某些 DELETE 场景下走全表扫描——因为外键约束要求引用值唯一,它需要快速确认“这个值只存在一份”,而普通索引无法保证这点。


















