Mysql数据库多表设计
数据库表设计:一对一、一对多、多对多
在关系型数据库中,表与表之间的关系常见三种:一对一、一对多、多对多。
1. 一对一(One-to-One)
概念
一条记录对应另一张表的唯一一条记录,常用于把“主表”和“扩展信息表”拆分存储。
示例:用户信息表 + 用户身份证表
-- 用户表
CREATE TABLE tb_user (id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',name VARCHAR(10) NOT NULL COMMENT '姓名',gender TINYINT UNSIGNED NOT NULL COMMENT '性别, 1 男 2 女',phone CHAR(11) COMMENT '手机号',degree VARCHAR(10) COMMENT '学历'
) COMMENT '用户信息表';-- 用户身份证表
CREATE TABLE tb_user_card (id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',nationality VARCHAR(10) NOT NULL COMMENT '民族',birthday DATE NOT NULL COMMENT '生日',idcard CHAR(18) NOT NULL COMMENT '身份证号',issued VARCHAR(20) NOT NULL COMMENT '签发机关',expire_begin DATE NOT NULL COMMENT '有效期限-开始',expire_end DATE COMMENT '有效期限-结束',user_id INT UNSIGNED NOT NULL UNIQUE COMMENT '用户ID',CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id)
) COMMENT '用户身份证表';
设计要点:
tb_user
存储基本信息,tb_user_card
存储扩展信息。tb_user_card.user_id
设置UNIQUE
保证一对一关系。- 外键
FOREIGN KEY
保证数据一致性。
2. 一对多(One-to-Many)
概念
一张表的一条记录,对应另一张表的多条记录,最常见的关系(父子关系)。
示例:部门表 + 员工表
-- 部门表
CREATE TABLE tb_dept (id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',name VARCHAR(10) NOT NULL UNIQUE COMMENT '部门名称',create_time DATETIME COMMENT '创建时间',update_time TIMESTAMP COMMENT '更新时间'
) COMMENT '部门表';-- 员工表
CREATE TABLE tb_emp (id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工id',username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',name VARCHAR(10) NOT NULL COMMENT '姓名',gender TINYINT COMMENT '性别,1男 | 2女',dept_id INT UNSIGNED COMMENT '部门ID',entrydate DATE COMMENT '入职时间',CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES tb_dept(id)ON UPDATE CASCADEON DELETE SET NULL
) COMMENT '员工表';
设计要点:
tb_dept.id
是主表主键,tb_emp.dept_id
是外键。- 一个部门可以对应多名员工。
ON DELETE SET NULL
避免删除部门时导致员工记录丢失。
3. 多对多(Many-to-Many)
概念
一张表的一条记录可以对应另一张表的多条记录,反之亦然,需通过中间表实现。
示例:学生表 + 课程表 + 中间表
非常好的问题!
答案是:可以同时存在主键 id
和联合唯一索引,但不能同时存在两个主键。
我帮你详细拆解一下,让你彻底理解。
在多对多中间表中,有两种常见设计方式:
方案 1:联合主键(推荐,最简洁)
CREATE TABLE tb_student_course (student_id INT NOT NULL,course_id INT NOT NULL,PRIMARY KEY (student_id, course_id), -- 联合主键CONSTRAINT fk_student FOREIGN KEY (student_id) REFERENCES tb_student(id),CONSTRAINT fk_course FOREIGN KEY (course_id) REFERENCES tb_course(id)
);
✅ 优点:
- 主键天然保证
student_id + course_id
唯一。 - 不需要额外字段,节省空间。
- 查询性能好,直接走联合主键索引。
❌ 缺点:
- 如果业务需要给这条“关系”单独添加扩展字段(比如选课时间、选课状态),插入、更新需要带上两个字段作为条件,不如单独
id
简洁。
方案 2:自增 id + 联合唯一索引(也常用)
CREATE TABLE tb_student_course (id INT PRIMARY KEY AUTO_INCREMENT, -- 自增主键student_id INT NOT NULL,course_id INT NOT NULL,UNIQUE KEY uq_student_course (student_id, course_id), -- 联合唯一索引CONSTRAINT fk_student FOREIGN KEY (student_id) REFERENCES tb_student(id),CONSTRAINT fk_course FOREIGN KEY (course_id) REFERENCES tb_course(id)
);
✅ 优点:
id
作为单列主键,方便引用(比如其他表还要引用这条记录)。- 插入更新时可以用
id
操作,而不是带两个字段。 - 适合中间表需要扩展更多业务字段(选课时间、成绩、状态等)。
❌ 缺点:
- 多一个冗余字段,占用一点存储空间。
- 需要额外创建联合唯一索引,才能防止重复数据。
能不能两个主键同时存在?
不能。
MySQL 规定一张表只能有一个主键,即使是联合主键,也是一个主键,只是由多个列组成。所以不能同时有:
id INT AUTO_INCREMENT PRIMARY KEY, -- 单列主键
PRIMARY KEY (student_id, course_id) -- 联合主键
这样会报错:“Multiple primary key defined”。
推荐做法
场景 | 推荐方案 |
---|---|
纯粹的关联关系表,不需要额外字段 | 方案 1:联合主键 |
需要扩展字段(选课时间、成绩、状态)或需要单独引用此记录 | 方案 2:自增 id + 联合唯一索引 |
-- 学生表
CREATE TABLE tb_student (id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',name VARCHAR(10) COMMENT '姓名',no VARCHAR(10) COMMENT '学号'
) COMMENT '学生表';-- 课程表
CREATE TABLE tb_course (id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',name VARCHAR(10) COMMENT '课程名称'
) COMMENT '课程表';-- 学生课程中间表
CREATE TABLE tb_student_course (student_id INT NOT NULL COMMENT '学生ID',course_id INT NOT NULL COMMENT '课程ID',PRIMARY KEY (student_id, course_id), -- 联合主键,防止重复选课CONSTRAINT fk_courseid FOREIGN KEY (course_id) REFERENCES tb_course(id)ON DELETE CASCADE ON UPDATE CASCADE,CONSTRAINT fk_studentid FOREIGN KEY (student_id) REFERENCES tb_student(id)ON DELETE CASCADE ON UPDATE CASCADE
) COMMENT '学生课程中间表';
设计要点:
- 中间表
tb_student_course
存两张主表的主键。 - 可以加联合唯一索引
(student_id, course_id)
避免重复选课。 - 通过
JOIN
查询多对多关系。
4. 三种关系对比
关系类型 | 特点 | 表设计关键点 |
---|---|---|
一对一 | 一条记录对应另一张表唯一一条记录 | 外键 + UNIQUE |
一对多 | 一条记录对应另一张表多条记录 | 外键 (多端表保存外键) |
多对多 | 两张表多对多互相关联 | 需要中间表 + 两个外键 |
✅总结
- 一对一 → 外键 + UNIQUE
- 一对多 → 外键放在“多”的一端
- 多对多 → 用中间表拆成两个一对多