【MySQL 笔记】表的约束(下)—— 外键详解 + 级联行为 + 约束 vs 索引 + 生产常见坑
上一期我们把基础约束(NOT NULL、DEFAULT、UNIQUE、PRIMARY KEY、CHECK、AUTO_INCREMENT)过了一遍,今天重点把最复杂、也最有争议的外键约束(FOREIGN KEY)讲透,顺带对比约束与索引的区别,以及生产环境中大家最容易踩的坑。
一、外键约束核心特性速查
| 特性 | 说明 | 是否强制 | 性能影响 | 生产推荐度(2026视角) |
|---|---|---|---|---|
| 参照完整性 | 从表字段值必须在主表对应字段中存在(或为NULL) | 是 | 中等 | — |
| 允许NULL | 外键字段通常允许NULL(表示“无关联”) | — | — | 常见 |
| 级联操作 | 主表删除/更新时,从表可自动跟随(CASCADE、SET NULL 等) | 可选 | 有影响 | 谨慎使用 |
| 自动建索引 | InnoDB 会在外键字段上自动创建普通索引(如果没有合适索引) | 是 | 有利 | — |
| 约束名 | 可自定义名称,便于后期删除/修改 | 推荐 | — | 强烈建议 |
| 跨库/跨引擎 | 外键必须在同一数据库,且两表都用 InnoDB | — | — | — |
二、外键定义的两种写法
- 列级(简单,但无法指定级联行为)
user_id BIGINT,
FOREIGN KEY (user_id) REFERENCES users(id)
- 表级(推荐!可起名、可定义级联)
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(32) UNIQUE NOT NULL,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_orders_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE RESTRICT -- 默认行为
ON UPDATE CASCADE
) ENGINE=InnoDB;
三、级联行为详解(ON DELETE / ON UPDATE)
| 选项 | DELETE 时行为 | UPDATE 时行为 | 适用场景 | 风险/注意事项 |
|---|---|---|---|---|
| RESTRICT / NO ACTION | 禁止删除(默认) | 禁止修改主表主键 | 保护数据完整性 | 最安全,但业务删除会报错 |
| CASCADE | 主删从也删 | 主改从跟着改 | 彻底清理关联数据(如删除用户删订单) | 误删风险高,级联可能很多行 |
| SET NULL | 主删 → 从表外键字段设为 NULL | 主改 → 从表外键设为 NULL | “可选关联”场景 | 从表字段必须允许 NULL |
| SET DEFAULT | 主删 → 从表外键设为 DEFAULT 值 | 主改 → 从表设为 DEFAULT 值 | 极少用 | DEFAULT 必须合理 |
最常见组合推荐(生产中):
-- 强保护型(推荐大多数业务)
ON DELETE RESTRICT ON UPDATE CASCADE
-- 自动清理型(用户相关数据)
ON DELETE CASCADE ON UPDATE CASCADE
-- 可选关联型(比如优惠券使用记录)
ON DELETE SET NULL ON UPDATE CASCADE
四、约束 vs 索引 对比(面试常考)
| 项目 | 约束(Constraint) | 索引(Index) | 谁更重? | 备注 |
|---|---|---|---|---|
| 目的 | 保证数据正确性、一致性 | 加速查询 | 约束 > 索引 | 约束是业务规则,索引是性能优化 |
| 唯一约束 UNIQUE | 强制唯一 + 自动建唯一索引 | 只是加速 + 允许重复(普通索引) | — | UNIQUE 约束 = 唯一索引 + 唯一性校验 |
| 外键 | 强制参照完整性 + 自动建普通索引 | 单独建索引不强制参照完整性 | 约束更强 | 外键依赖索引,但索引不等于外键 |
| 删除约束 | ALTER TABLE DROP FOREIGN KEY … | ALTER TABLE DROP INDEX … | — | 约束名 vs 索引名 |
| 性能开销 | 插入/更新/删除时校验(外键最明显) | 插入/更新时维护索引 | 约束稍大 | 大表外键慎用 |
| 可否禁用 | SET FOREIGN_KEY_CHECKS=0(临时关闭) | 不可直接禁用 | — | 用于批量导入数据 |
一句话总结:
约束 = 业务规则 + 附带索引
索引 = 纯性能工具
五、生产中最容易踩的 10 个外键/约束坑(真实血泪)
- 忘记 ENGINE=InnoDB(MyISAM 不支持外键)
- 主表字段不是 PRIMARY KEY 或 UNIQUE(外键参照必须是主键或唯一键)
- 字段类型不一致(int vs bigint、unsigned vs signed)
- 大表加外键导致锁表时间过长(尤其是已有大量数据时)
- 误用 CASCADE 导致连锁删除(删一个用户把几十万订单全删了)
- 批量导入数据时不关外键检查(超慢或报错)
SET FOREIGN_KEY_CHECKS=0;
-- 导入...
SET FOREIGN_KEY_CHECKS=1;
- 忘记给外键字段建索引(虽然自动建,但有时名字对不上)
- 约束名重复或没起名,导致后期 DROP 约束很麻烦
- 跨库外键(不可能,MySQL 不支持)
- 8.0 前用 CHECK 约束(只是摆设,不生效)
六、推荐的现代建表约束模板(2026 年风格)
CREATE TABLE order_items (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
quantity INT UNSIGNED NOT NULL DEFAULT 1,
unit_price DECIMAL(10,2) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id)
REFERENCES orders(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id)
REFERENCES products(id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
INDEX idx_order_id (order_id),
INDEX idx_product_id (product_id)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
COMMENT='订单明细表';
下一期预告建议:MySQL 索引全家桶(上)—— 索引类型 + 创建原则 + 复合索引最左前缀
有外键级联写法、禁用外键场景、或具体业务表结构想讨论的,欢迎留言~
祝大家约束加得合理,数据一致性拉满!🔒