外键字段无索引会导致关联查询极慢且易锁表,因InnoDB不自动为普通外键列建索引,缺失时父表删除需子表全表扫描;应手动添加单列或复合索引,并避免滥用ON DELETE CASCADE及在高并发表中使用外键。

外键字段没索引,查得慢还锁表
MySQL InnoDB 要求外键列必须有索引,但它只在你显式定义 PRIMARY KEY 或 UNIQUE 时自动建索引;普通外键列(比如 orders.customer_id)默认不带索引。没索引时,DELETE FROM customers WHERE id = 123 会触发子表全表扫描找依赖记录,既慢又容易锁住整个子表。
- 用
SHOW CREATE TABLE orders检查customer_id是否已有索引;没有就立刻加:ALTER TABLE orders ADD INDEX idx_customer_id (customer_id) - 复合外键(如
(order_id, product_id))必须建复合索引,且顺序要和外键定义一致,不能只建单列索引 - 避免重复索引:如果已有
INDEX(customer_id, status),再单独建INDEX(customer_id)就是冗余
ON DELETE CASCADE 在生产环境很危险
级联删除看着省事,但一条 DELETE 可能隐式触发几万行子表操作,事务时间拉长、锁持有时间变久、binlog 突然膨胀——线上见过因它导致主从延迟飙升的案例。
- 把
ON DELETE CASCADE全部换成ON DELETE RESTRICT,改由应用层控制删除节奏 - 真要删关联数据,用分批语句:
DELETE FROM order_items WHERE order_id IN (SELECT id FROM orders WHERE ... ) LIMIT 1000,循环执行 - 如果业务允许最终一致性,可发 MQ 消息异步清理,避开数据库压力峰值
批量导入时临时关外键检查,但别忘了开回来
SET FOREIGN_KEY_CHECKS = 0 不是性能调优手段,而是维护操作——它绕过所有外键校验,速度快,但一旦数据本身不满足约束,后续 SET FOREIGN_KEY_CHECKS = 1 会直接报错并拒绝启用,不是警告。
- 仅用于明确可控的场景:如
LOAD DATA INFILE、大表重建、ETL 导入 - 必须成对使用:
SET FOREIGN_KEY_CHECKS = 0; ... INSERT/UPDATE ... ; SET FOREIGN_KEY_CHECKS = 1; - 执行完立即验证:
SELECT * FROM child_table WHERE parent_id NOT IN (SELECT id FROM parent_table);,确保没脏数据
高并发写入表干脆去掉外键
订单流水、日志、埋点这类高频写入表,外键带来的锁竞争和校验开销远大于收益。InnoDB 的行锁在高并发下也扛不住外键引发的额外元数据查找和共享锁等待。
- 先确认业务是否真需要强一致性:比如“用户注销”必须同步清空其所有订单?还是允许几秒延迟?
- 去掉外键后,在应用层做轻量校验:插入前
SELECT 1 FROM users WHERE id = ?,失败则拒写 - 保留外键只用于低频、强一致要求的表(如配置表、权限表),其他表靠代码+监控兜底
外键不是开关,是权衡。真正卡住性能的,往往不是外键本身,而是没索引、乱级联、硬套在不适合的表上。动手前先看 EXPLAIN 和慢查日志里有没有 type: ALL 或 Extra: Using where; Using temporary; Using filesort —— 那才是该下手的地方。



















