主键(Primary Key)和外键(Foreign Key)的区别 是 Java + MySQL 开发中最基础也是最重要的数据库概念之一。下面用清晰的表格 + 详细解释,帮助你彻底搞懂:
1. 一目了然的对比表
| 对比维度 | 主键 (Primary Key) | 外键 (Foreign Key) |
|---|---|---|
| 作用 | 唯一标识本表的一条记录 | 建立两张表之间的关联关系 |
| 是否允许为空 | 不允许(NOT NULL) | 允许(除非你加 NOT NULL) |
| 是否唯一 | 必须唯一 | 可以重复(一个部门可以有多个员工) |
| 一张表能有几个 | 最多只能有1个(复合主键也算一个) | 一张表可以有多个外键 |
| 是否自动创建索引 | 是(主键索引,聚簇索引) | 是(普通索引,非聚簇) |
| 能否被其他表引用 | 可以(被其他表的外键引用) | 不能再被其他表的外键引用(除非它同时也是主键) |
| 对数据的影响 | 插入时必须保证唯一且不为空 | 插入/修改时会检查引用完整性(参照完整性约束) |
| 删除/更新时的行为 | 一般不会删除主键记录(或级联删除) | 有4种约束动作:RESTRICT / CASCADE / SET NULL / SET DEFAULT |
2. 详细解释
主键 (Primary Key)
- 核心作用:唯一标识表中的每一条记录(就像人的身份证号)。
- 特点:
- 不能重复、不能为空。
- MySQL 中主键会自动创建聚簇索引(数据物理存储顺序按主键排序),查询速度最快。
- 推荐使用:
INT/BIGINT AUTO_INCREMENT或UUID(视业务而定)。 - 示例:
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 主键
username VARCHAR(50) UNIQUE NOT NULL,
...
);
外键 (Foreign Key)
- 核心作用:维护两个表之间的参照完整性(Referential Integrity)。
- 特点:
- 外键的值必须来自被引用表的主键(或唯一键)。
- 防止“孤儿记录”(比如删除了一个部门,结果还有员工属于这个部门)。
- 示例(经典:部门-员工):
CREATE TABLE departments (
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(50) NOT NULL
);
CREATE TABLE employees (
emp_id BIGINT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(50) NOT NULL,
dept_id INT, -- 外键
CONSTRAINT fk_emp_dept
FOREIGN KEY (dept_id)
REFERENCES departments(dept_id)
ON DELETE RESTRICT -- 或 CASCADE / SET NULL
ON UPDATE CASCADE
);
3. 外键的 4 种约束动作(非常重要!)
当你对父表(departments)进行 DELETE 或 UPDATE 时,MySQL 如何处理子表(employees):
| 动作 | 含义 | 推荐场景 |
|---|---|---|
RESTRICT / NO ACTION | 禁止删除/修改(默认) | 数据安全要求高 |
CASCADE | 级联删除/更新(父删子也删) | 日志、临时数据 |
SET NULL | 把子表的外键字段设为 NULL | 允许员工不属于任何部门 |
SET DEFAULT | 把子表的外键设为默认值(MySQL 不常用) | – |
4. Java 开发中实际使用建议
- 强烈推荐使用外键(除非性能极致要求):
- 防止脏数据
- 代码更清晰(JPA/Hibernate 会自动映射关联关系)
- 高并发、大数据量场景 常见做法:
- 开发环境、测试环境:保留外键(便于发现问题)
- 生产环境:去掉外键约束,只在代码层面(Service 层)保证完整性 + 建立普通索引
- 理由:外键会导致级联锁表,影响并发性能
- JPA/Hibernate 写法示例:
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id", foreignKey = @ForeignKey(name = "fk_emp_dept"))
private Department department;
}
总结(一句话记住):
- 主键 = “我是谁”(本表唯一标识)
- 外键 = “我爸是谁”(指向另一个表的标识)
主键解决本表记录的唯一性,外键解决表与表之间的关联完整性。
需要我再给你补充:
- 复合主键 vs 单列主键?
- 物理外键 vs 逻辑外键(代码控制)性能对比?
- 实际项目中如何优雅处理外键?
随时问我!