My App

多表关系

学习目标

  • 理解什么是一对多关系,能举出业务场景(如部门与员工)。
  • 掌握一对多在数据库中的实现方式:在“多”的一方添加字段,关联“一”的一方主键。
  • 能看懂并写出 dept(部门)与 emp(员工)这类一对多表结构及建表语句。
  • 知道父表 / 子表主键 / 外键在一对多中的角色。

1. 什么是多表关系?

在实际业务中,数据往往分散在多张表里,表与表之间通过字段关联形成关系。常见有三种:

  • 一对多(1 : N):一个 A 对应多个 B,例如一个部门下有多个员工。
  • 多对多(M : N):多个 A 对应多个 B,例如学生与课程(一个学生选多门课,一门课被多个学生选),通常通过中间表实现。
  • 一对一(1 : 1):一个 A 对应一个 B,例如用户与用户详情表。

本笔记重点讲 一对多,它是业务中最常见、也最容易上手的一种。

2. 一对多(1 : N)概念

2.1 场景

典型场景:部门与员工的关系 —— 一个部门下有多个员工

  • “一”的一方:部门(dept) —— 学工部、教研部、咨询部等。
  • “多”的一方:员工(emp) —— 每个员工属于一个部门,多个员工可以属于同一个部门。

1

2.2 父表与子表

  • 父表(一的一方):部门表 dept,存储部门信息,主键为 id
  • 子表(多的一方):员工表 emp,存储员工信息,其中有一个字段 dept_id 用来“指向”部门表的主键 id

关系可以理解为:子表中的多条记录,通过外键字段关联到父表中的一条记录

2.3 一对多如何实现?

核心结论:在数据库表中,在“多”的一方添加一个字段,用来关联“一”的一方的主键

也就是说:

  • 在**员工表(多的一方)**里增加字段 dept_id
  • dept_id 的取值来源于 部门表(一的一方)的主键 id
  • 这样,每条员工记录都能通过 dept_id 找到自己所属的部门;一个部门 id 可以被多条员工记录引用,形成“一对多”。

若在数据库层面要强制“员工所属部门必须存在”,可以为 dept_id 添加 外键约束(FOREIGN KEY),引用 dept(id)。是否建外键可根据项目规范决定(有的团队在应用层校验,不在库中建外键)。

3. 示例:部门表(dept)与员工表(emp)

下面用你提供的 部门表 dept员工表 emp 作为一对多示例。

3.1 父表:部门表 dept(一的一方)

CREATE TABLE dept (
  id          INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID, 主键',
  name        VARCHAR(10) NOT NULL UNIQUE COMMENT '部门名称',
  create_time DATETIME DEFAULT NULL COMMENT '创建时间',
  update_time DATETIME DEFAULT NULL COMMENT '修改时间'
) COMMENT '部门表';
  • 主键id,唯一标识一个部门。
  • 示例数据:学工部(1)、教研部(2)、咨询部(3)、就业部(4)、人事部(5)、行政部(15) 等。

3.2 子表:员工表 emp(多的一方)

多的一方增加 dept_id,用于关联部门表主键 id

CREATE TABLE emp (
  id          INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID, 主键',
  username    VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
  password    VARCHAR(32) DEFAULT '123456' COMMENT '密码',
  name        VARCHAR(10) NOT NULL COMMENT '姓名',
  gender      TINYINT UNSIGNED NOT NULL COMMENT '性别, 1:男, 2:女',
  phone       CHAR(11) NOT NULL UNIQUE COMMENT '手机号',
  job         TINYINT UNSIGNED COMMENT '职位, 1班主任, 2讲师, 3学工主管, 4教研主管, 5咨询师',
  salary      INT UNSIGNED COMMENT '薪资',
  image       VARCHAR(255) COMMENT '头像',
  entry_date  DATE COMMENT '入职日期',
  dept_id     INT UNSIGNED COMMENT '部门ID',   -- 关联 dept.id,实现一对多
  create_time DATETIME COMMENT '创建时间',
  update_time DATETIME COMMENT '修改时间'
) COMMENT '员工表';
  • 主键id,唯一标识一个员工。
  • 外键字段dept_id,表示“该员工属于哪个部门”,取值应对应 dept.id
  • 多条员工记录的 dept_id 可以相同(例如多个人都属于教研部 dept_id=2),从而形成“一个部门对应多个员工”的一对多关系。

3.3 关系示意(对应你提供的图示)

  • 父表 dept:一的一方,主键 id
  • 子表 emp:多的一方,字段 dept_id 指向 dept.id
  • 箭头:从 dept.id 指向 emp.dept_id,表示“多的一方通过外键关联一的一方的主键”。

示例数据对应关系:

  • dept_id = 1(学工部)→ 多条员工记录(如扈三娘、柴进、李逵等)。
  • dept_id = 2(教研部)→ 多条员工记录(如施耐庵、宋江、卢俊义等)。
  • dept_id = 3(咨询部)→ 多条员工记录(如阮小五、阮小七等)。

3.4 可选:为 dept_id 添加外键约束

若要在数据库层面保证“员工所属部门必须存在”,可以给 emp.dept_id 加外键:

ALTER TABLE emp
  ADD CONSTRAINT fk_emp_dept
  FOREIGN KEY (dept_id) REFERENCES dept(id);
  • 含义:emp.dept_id 的取值必须在 dept.id 中存在。
  • 这种在数据库中用 FOREIGN KEY 定义的外键,称为物理外键;与之相对的是在业务代码里做关联校验的逻辑外键,见下文。

3.5 物理外键与逻辑外键

表与表之间的“关联”有两种常见做法:物理外键(在数据库中建外键约束)和逻辑外键(只在业务层维护关联关系)。

2

物理外键

  • 概念:使用 FOREIGN KEY 在数据库层面定义外键,直接关联另一张表。
  • 缺点
    • 影响增、删、改的效率:每次操作都要检查外键关系(如插入/更新子表要查父表是否存在,删除父表要检查是否有子表引用)。
    • 仅适用于单节点数据库:分布式、分库分表、读写分离等场景下,外键约束难以维护,一般不适用。
    • 容易引发死锁、消耗性能:多表并发修改时,外键检查会加锁,可能造成死锁或性能下降。

逻辑外键

  • 概念不在表上建 FOREIGN KEY,只在业务层逻辑中保证“关联关系”正确(例如插入/更新员工时,在 Java 代码里校验 dept_id 是否在部门表中存在)。
  • 优点:通过逻辑外键,可以避免上述物理外键带来的效率、分布式、死锁等问题;表结构更灵活,便于分库分表和水平扩展。
  • 注意:数据一致性完全由应用层保证,需要在业务代码里做好校验与约束,否则可能出现“脏数据”(如 dept_id 指向不存在的部门)。

实践建议:很多互联网项目采用逻辑外键(表上有 dept_id 字段,但不建 FOREIGN KEY),由应用层保证关联正确;传统单体、强一致性场景下仍可能使用物理外键。团队可根据业务场景、是否分布式、对性能与一致性的要求来选型。

4. 一对一(1 : 1)

4.1 应用场景与单表拆分

一对一关系在实际开发中应用起来比较简单,常用于单表拆分:将一张大表拆成两张小表,把大表中的基础字段放在一张表里,把其他字段放在另一张表里,从而提高数据的操作效率。

典型应用场景:用户表(基本信息 + 身份信息)

  • 一张表存用户基本信息(姓名、性别、手机号、学历等)。
  • 另一张表存用户身份信息(民族、生日、身份证号、签发机关、有效期限等)。
  • 两者通过一对一关联(一个用户对应一条身份信息)。

4.2 一对一如何实现?

实现方式:在任意一方添加外键,关联另一方的主键,并给该外键加上 UNIQUE 约束,保证“一一对应”。

4.3 示例:用户基本信息表与用户身份信息表

用户基本信息表 tb_user

-- 用户基本信息表
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 '用户基本信息表';

-- 测试数据
INSERT INTO tb_user VALUES (1,'白眉鹰王',1,'18812340001','初中'),
                           (2,'青翼蝠王',1,'18812340002','大专'),
                           (3,'金毛狮王',1,'18812340003','初中'),
                           (4,'紫衫龙王',2,'18812340004','硕士');

用户身份信息表 tb_user_card

身份信息表中增加 user_id,关联 tb_user(id),并设置 UNIQUE,保证一个用户只有一条身份信息:

-- 用户身份信息表
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 '用户身份信息表';

-- 测试数据
INSERT INTO tb_user_card VALUES (1,'汉','1960-11-06','100000100000100001','朝阳区公安局','2000-06-10',NULL,1),
        (2,'汉','1971-11-06','100000100000100002','静安区公安局','2005-06-10','2025-06-10',2),
        (3,'汉','1963-11-06','100000100000100003','昌平区公安局','2006-06-10',NULL,3),
        (4,'回','1980-11-06','100000100000100004','海淀区公安局','2008-06-10','2028-06-10',4);
  • user_id:外键,关联 tb_user(id)UNIQUE 保证一个用户只对应一条身份记录,形成一对一。
  • 若采用逻辑外键,可不建 FOREIGN KEY,在业务层保证 user_idtb_user 中存在且唯一即可。

5. 多对多(M : N)

5.1 场景

多对多关系在开发中也比较常见,例如:

  • 学生与老师:一个学生可以有多个授课老师,一个老师也可以教多个学生。
  • 学生与课程:一个学生可以选修多门课程,一门课程也可以供多个学生选修。

5.2 多对多如何实现?

实现方式:建立一张中间表,中间表中至少包含两个外键字段,分别关联两方的主键

也就是说:

  • 学生表课程表各自独立,不直接互相引用。
  • 新增学生课程表(中间表):每条记录表示“某学生选了某课程”,通过 student_id 关联学生表主键、course_id 关联课程表主键。
  • 这样,一个学生可以对应中间表多条记录(选多门课),一门课程也可以对应中间表多条记录(被多个学生选),形成多对多。

5.3 示例:学生表、课程表与中间表

3

学生表 tb_student

-- 学生表
CREATE TABLE tb_student (
    id   INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
    name VARCHAR(10) COMMENT '姓名',
    no   VARCHAR(10) COMMENT '学号'
) COMMENT '学生表';

-- 学生表测试数据
INSERT INTO tb_student(name, no) VALUES ('黛绮丝', '2000100101'),
                                        ('谢逊', '2000100102'),
                                        ('殷天正', '2000100103'),
                                        ('韦一笑', '2000100104');

课程表 tb_course

-- 课程表
CREATE TABLE tb_course (
    id   INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
    name VARCHAR(10) COMMENT '课程名称'
) COMMENT '课程表';

-- 课程表测试数据
INSERT INTO tb_course (name) VALUES ('Java'), ('PHP'), ('MySQL'), ('Hadoop');

学生课程表(中间表)tb_student_course

中间表包含两个外键:student_id 关联学生表主键,course_id 关联课程表主键。

-- 学生课程表(中间表)
CREATE TABLE tb_student_course (
    id         INT AUTO_INCREMENT COMMENT '主键' PRIMARY KEY,
    student_id INT NOT NULL COMMENT '学生ID',
    course_id  INT NOT NULL COMMENT '课程ID',
    CONSTRAINT fk_courseid  FOREIGN KEY (course_id)  REFERENCES tb_course(id),
    CONSTRAINT fk_studentid  FOREIGN KEY (student_id) REFERENCES tb_student(id)
) COMMENT '学生课程中间表';

-- 学生课程表测试数据
INSERT INTO tb_student_course(student_id, course_id) VALUES (1,1),(1,2),(1,3),(2,2),(2,3),(3,4);
  • student_id:外键,关联 tb_student(id)
  • course_id:外键,关联 tb_course(id)
  • 一条中间表记录表示“学生 student_id 选修了课程 course_id”;多条记录可共享同一 student_id(一个学生多门课)或同一 course_id(一门课多个学生),形成多对多。
  • 若采用逻辑外键,可不建 FOREIGN KEY,在业务层保证 student_idcourse_id 在对应表中存在即可;中间表也可增加联合唯一约束 UNIQUE(student_id, course_id),防止同一学生重复选同一门课。

6. 小结与记忆要点

  • 一对多:一个 A 对应多个 B;在多的一方增加字段关联一的一方的主键(如 emp.dept_iddept.id)。
  • 一对一:一个 A 对应一个 B;在任意一方增加外键关联另一方主键,并设置外键为 UNIQUE(如 tb_user_card.user_idtb_user.id)。
  • 多对多:多个 A 对应多个 B;需要建立一张中间表,中间表中有两个外键字段,分别关联两方的主键(如 tb_student_course.student_idtb_student.idtb_student_course.course_idtb_course.id)。
  • 物理外键 vs 逻辑外键:物理外键用 FOREIGN KEY 在库中约束;逻辑外键只在业务层校验,表结构更灵活,适合分布式场景。

On this page