数据库设计——巧用“中间表”解决多对多关系
摘要:什么是多对多关系?拿我们最熟悉的选课系统来举例:选课了,张三选了:《高等数学》、《大学英语》、《编程基础》;李四选了:《高等数学》、《编程基础》;那么《编程基础》这门课有:张三、李四两个学生。这就是一个典型的双向多对多关系:一个学生可以选多门课,一门课也可以被多个学生选。此时我们准备了两张表,stu…
什么是多对多关系?
拿我们最熟悉的选课系统来举例:选课了,张三选了:《高等数学》、《大学英语》、《编程基础》;李四选了:《高等数学》、《编程基础》;那么《编程基础》这门课有:张三、李四两个学生。
这就是一个典型的双向多对多关系:一个学生可以选多门课,一门课也可以被多个学生选。
此时我们准备了两张表,student(学生)表和course(课程)表,为了实现相互映射,大聪明同学决定在某一个表里通过 ',' 的方式创建一个字段用来记录这个关系。
比如我们在student里添加course字段,然后通过'1,2,3,4,5'课程号来记录学生的选课;或是在course里加入students字段,然后通过'1,2,3,4,5'学生号来记录课程参与的学生。此时便会出现以下四个问题:
1. 查询困难:想找“选了编程基础课的所有学生”,需要遍历所有记录,解析字符串
2. 更新麻烦:张三退了编程基础课,需要从"1,3,5"中精确移除"3"而不影响其他
3. 数据不一致:如果两个地方都存,很容易出现张三的课程列表说有课,但课程的学生列表里没张三
4. 无法加属性:学生某门课的成绩、选课时间存哪里?
那么该如何解决这个问题呢?
我们可以巧用“中间表”来解决这个问题!
比如,我们可以创建这样一张表student_course:
CREATE TABLE student_course ( student_id BIGINT NOT NULL, course_id BIGINT NOT NULL, score DECIMAL(4,1) COMMENT '成绩,如 89.5', select_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '选课时间', PRIMARY KEY (student_id, course_id), -- 联合主键 KEY idx_course_id (course_id) -- 加速反向查询 ) COMMENT='学生选课关系表';
它的表结构如下:

通过“中间表”,我们可以直接地把关系变成数据,让我们对两张表的CRUD操作变得更加简单!
比如有这样一张student_course表:
| student_id | course_id | score | select_time |
|---|---|---|---|
| 101 (张三) | 1 (高数) | 92.0 | 2025-09-01 |
| 101 (张三) | 3 (编程) | 88.5 | 2025-09-01 |
| 102 (李四) | 1 (高数) | 85.0 | 2025-09-02 |
| 102 (李四) | 3 (编程) | 90.0 | 2025-09-02 |
我们就可以知道某人在什么时候选择了某科目,这样的表设计不仅能够提高我们CRUD操作,还极大减小了设计复杂度,让我们更从容地面对数据库设计。



