主键是表中每条记录的唯一标识,强制非空且唯一,用于精准定位数据;外键是跨表引用约束,确保子表字段值在主表中存在,维护数据一致性。

主键是表里每条记录的“身份证号”
主键(PRIMARY KEY)强制要求字段值非空且唯一,用来精准定位某一行数据。没有主键的表在实际业务中几乎无法可靠使用——比如你查一条订单,靠什么确认就是它?靠时间?靠金额?都可能重复。只有主键能给出确定性答案。
实操建议:
• 一张表最多一个主键,但可以是多个字段组成的复合主键(如 (order_id, item_seq));
• 推荐用自增整数(INT AUTO_INCREMENT)或 UUID(注意性能取舍);
• 别等表建完再加主键:ALTER TABLE orders ADD PRIMARY KEY (order_id); 会锁表,大表执行慢;
• 主键自动创建聚簇索引,直接影响查询和排序性能,选字段时要考虑高频查询条件。
外键是两张表之间的“关系契约”
外键(FOREIGN KEY)本身不标识本表记录,而是声明“这个字段的值,必须在另一张表的某个主键(或唯一键)里存在”。它的核心作用不是加速查询,而是守住数据一致性底线——防止出现“订单绑了个根本不存在的用户”这种逻辑错误。
常见错误现象:
• 插入子表记录时报错 Cannot add or update a child row: a foreign key constraint fails,大概率是引用的主表记录还没插入,或外键字段类型/长度不匹配(如主表 user_id INT,子表却定义为 user_id BIGINT);
• 删除主表记录失败,报错提示被外键引用——这不是 bug,是约束生效了;
• 忘记引擎:外键只在 InnoDB 中有效,MyISAM 完全忽略 FOREIGN KEY 语法,也不报错,极易埋坑。
实操建议:
• 外键字段和被引用字段必须严格一致:类型、符号性(SIGNED/UNSIGNED)、字符集、甚至是否允许 NULL;
• 明确设置级联行为,比如 ON DELETE CASCADE 或 ON DELETE SET NULL,否则删除主表前得手动清理子表;
• 外键会增加 INSERT/UPDATE/DELETE 开销,高并发写入场景需权衡;
• 备份恢复时注意顺序:必须先导入主表,再导入子表,否则外键校验失败。
主键和外键不是“功能互补”,而是“角色分工”
主键解决“我在哪”,外键解决“我和谁有关”。它们常一起出现,但目的完全不同:主键确保本表数据不混乱,外键确保跨表逻辑不脱节。混淆二者容易导致设计偏差——比如把外键当主键用(结果发现可为空、可重复),或在外键列上盲目建索引(其实 InnoDB 已自动为外键字段建索引,重复建反而浪费)。
关键差异点:
• 主键不可为 NULL,外键字段可以为 NULL(表示“暂未关联”);
• 一张表只能有一个主键,但可以有多个外键(如订单表同时有 user_id、product_id、address_id);
• 主键约束本质是 NOT NULL + UNIQUE 的组合,外键约束本质是跨表的引用检查;
• 外键依赖主键(或至少是唯一键),但主键完全不依赖外键。
真正容易被忽略的细节:约束名和禁用时机
MySQL 允许给约束起名,比如 CONSTRAINT fk_orders_user_id FOREIGN KEY (user_id) REFERENCES users(id)。名字看着可有可无,但一旦出错,错误信息里全是系统生成的随机名(如 fk_1a2b3c),排查效率骤降。更麻烦的是,有些运维操作(如在线 DDL 工具、某些备份还原流程)会因外键约束失败而中断,这时临时禁用约束不是删掉它,而是用 SET FOREIGN_KEY_CHECKS = 0; ——但必须配对开启,且仅限当前会话。
务必记住:
• 约束名最好见名知义,尤其多人协作时;
• FOREIGN_KEY_CHECKS = 0 是“手术刀”,不是“创可贴”,用完立刻恢复;
• 即使业务层做了校验,数据库层的外键仍有必要——网络延迟、并发竞争、代码漏判都会绕过应用层检查;
• 不要因为“ORM 框架能管理关系”就放弃外键,那是两层防御,不是重复劳动。










