学生选课系统只需student、course、selection三张表,需设外键、联合唯一索引及容量默认值;超限选课须用带条件insert校验余量;查已选课及成绩须left join;建库建表须统一utf8mb4字符集与时区。

学生选课系统需要哪些核心表结构
直接照搬教务系统模板容易字段冗余或漏关键约束。真实场景下,student、course、selection 三张表就够用,但必须加好外键和唯一索引。
-
student表至少含id(主键)、name、major;id建议用BIGINT UNSIGNED,避免后期扩容问题 -
course表要包含id、title、credit(学分)、capacity(最大容量),capacity必须设默认值,否则插入时可能为NULL导致逻辑出错 -
selection是关联表,字段为student_id、course_id、selected_at;必须设联合唯一索引UNIQUE KEY (student_id, course_id),否则同一学生可重复选同一门课 - 外键要显式启用:建表时加
FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE,不然删学生后选课记录残留
如何防止超限选课(并发场景下)
用 INSERT ... SELECT 或普通 INSERT 都无法原子性校验余量,高并发时极易超选。正确做法是把“查余量 + 插入”合并为一条带条件的语句。
- 用
INSERT INTO selection (student_id, course_id, selected_at) SELECT ?, ?, NOW() FROM course WHERE id = ? AND capacity > (SELECT COUNT(*) FROM selection WHERE course_id = ?) - 执行后检查
ROW_COUNT()是否为 1;为 0 说明已满员或课程不存在,不能靠 PHP/Python 层捕获异常来判断 - 别依赖
SELECT ... FOR UPDATE锁整行——course表锁会阻塞其他课程操作,且在读已提交(RC)隔离级别下仍可能幻读 - 如果业务要求实时显示余量,建议用触发器或应用层异步更新
course.remaining字段,避免每次查都算聚合
查询某学生已选课程及成绩(含未录入成绩的情况)
新手常写 INNER JOIN 导致没成绩的课直接消失,实际需求是“所有已选课”,成绩字段允许为空。
- 必须用
LEFT JOIN连接成绩表(假设叫grade),且ON条件只写关联字段:ON g.student_id = s.id AND g.course_id = c.id,不能把成绩非空判断写进ON - 常见错误是写成
WHERE g.score IS NOT NULL,这会让没录成绩的课被过滤掉 - 如果成绩表有历史版本(如重修),需加子查询或窗口函数取最新一条,例如:
(SELECT score FROM grade g2 WHERE g2.student_id = s.id AND g2.course_id = c.id ORDER BY updated_at DESC LIMIT 1) - MySQL 8.0+ 可用
ROW_NUMBER() OVER (PARTITION BY student_id, course_id ORDER BY updated_at DESC)预先标记最新记录
初始化测试数据时要注意字符集和时区
中文姓名、课程名乱码或时间戳偏差,90% 出在连接层没对齐,不是建表时的问题。
- 建库必须指定:
CREATE DATABASE school DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;utf8不支持 emoji 和部分生僻汉字 - 连接字符串里加参数:
?charset=utf8mb4&loc=Asia%2FShanghai(JDBC/PHP PDO 需对应调整),否则NOW()返回 UTC 时间 - 批量导入 SQL 文件前,先执行
SET NAMES utf8mb4;,否则 source 命令可能按 latin1 解析 - 测试数据中避免用
sysdate()或curdate()生成时间——不同服务器时区不一致会导致断言失败,改用固定时间如'2024-09-01 08:00:00'
selection 表的联合索引顺序:必须是 (student_id, course_id) 而非反过来,否则按学生查选课列表时无法走索引。










