资讯动态

学生选课系统数据库设计:从ER模型到MySQL事务与并发控制

发布时间:2026/9/12 15:58:34 来源:尧图企业网站定制
简介面向数据库课程设计学习者这份某高校学生选课系统的设计资料包以学生选课场景为载体完整呈现数据库课程设计从需求分析、概念结构设计到逻辑结构设计与物理实现的典型过程适合需要完成相似课设或巩固数据库原理的本科及高职学生参考。压缩包内共3个文件其中Word版课程设计报告doc详述系统分析、ER图、关系模式及设计思路SQL脚本sql提供建库建表与基础数据数据库备份bak便于直接还原查看运行效果整体仅802KB轻量易用。资源已有998人学习下载口碑较好属于高分数课设。通过该资源读者可学到选课系统涉及的学生、课程、成绩等核心实体建模方法掌握数据库定义、完整性约束设置与SQL编程技巧并借鉴规范化报告写作框架为独立完成课程设计提供有力支撑。1. 学生选课系统课程设计从一张成绩单反推表结构拿到“某高校学生选课系统的设计”这个数据库课程设计题目大多数人第一反应是建三张表、写几个增删改查页面。但真正拉开差距的不在“能不能跑”而在“跑起来之后还能不能守住业务规则”同一门课只剩一个名额时并发选课会不会超员、退课后成绩记录能不能追溯、学生能不能查到同一学期的课程冲突。这篇博文按做课程设计最常见的 MySQL 方案从 ER 模型、三张核心表、存储过程、触发器一路走到事务、权限和答辩演示验证把一套可以完整复现的路线讲清楚。适合拿了这题想认真做完而不是临交差前复制粘贴的同学。2. 学生选课系统的数据库设计ER模型、范式与核心表结构2.1 从需求描述到实体-联系图选课业务里必须画清楚的三个实体写这个课程设计我一般会先让学生在纸上画 ER 图而不是直接打开 MySQL。学生、课程、选课记录三个实体是骨架可选实体还有院系、教师、教室。最容易画错的地方是选课记录它不是学生和课程之间的纯粹连接表它自己就是实体承担成绩、选课时间、退课状态这些属性。评分看数据模型的人第一眼就看你有没有把选课记录当作实体对待。画完实体还要标函数依赖。学号决定姓名、性别、学院课程号决定课程名、学分、教师、容量(学号, 课程号)决定选课时间和成绩。凡是不完全依赖主键、或者存在传递依赖的字段都要拆出去。比如教师职称和教师所属院系如果在课程表里就会产生传递依赖要拆成教师表课程表只保留教师 ID。这也是数据库面试题里反复问的“范式”在这个题目里最实在的落点。2.2 第三范式下的核心表结构学生、课程、选课三大表的字段取舍下面这套结构是我做这个课程设计时最常用的一版以 MySQL 8.0 为基准语法上也兼容很多其他数据库。学生表studentstu_id CHAR(10) PK学号用定长字符而不是 INT学号是业务编号不是数值不参与数学运算stu_name VARCHAR(20) NOT NULLgender ENUM(M,F)major VARCHAR(50)专业grade SMALLINT UNSIGNED年级比如 2024enroll_date DATE入学时间课程表coursecourse_id CHAR(6) PKcourse_name VARCHAR(50) NOT NULLcredit DECIMAL(2,1)学分支持 3.5 这种小数teacher_id CHAR(6)教师编号关联教师表capacity SMALLINT UNSIGNED DEFAULT 30课程容量selected_count SMALLINT UNSIGNED DEFAULT 0已选人数selected_count是冗余字段它能避免每次选课都去COUNT(*)全表扫一遍但冗余字段必须在写入时同步维护否则会出现不一致。这个矛盾后面用触发器兜底。选课表enrollment的字段设计字段类型说明stu_idCHAR(10)学号复合主键之一course_idCHAR(6)课程号复合主键之一enroll_timeDATETIME选课时间默认当前时间gradeDECIMAL(4,1)成绩允许为空statusENUM(selected,dropped)选课状态退课后保留记录status字段是很多人会漏掉的设计退课不应该物理删除选课记录否则成绩数据、选课历史全没了。用状态标记才能回答“这学生退过哪些课”这类问题。2.3 建库建表 SQL 完整脚本课程设计第一版可执行代码CREATE DATABASE IF NOT EXISTS course_db DEFAULT CHARSET utf8mb4; USE course_db; CREATE TABLE student ( stu_id CHAR(10) NOT NULL, stu_name VARCHAR(20) NOT NULL, gender ENUM(M, F) DEFAULT M, major VARCHAR(50) NOT NULL, grade SMALLINT UNSIGNED, enroll_date DATE, PRIMARY KEY (stu_id) ); CREATE TABLE course ( course_id CHAR(6) NOT NULL, course_name VARCHAR(50) NOT NULL, credit DECIMAL(2,1) DEFAULT 2.0, teacher_id CHAR(6), capacity SMALLINT UNSIGNED DEFAULT 30, selected_count SMALLINT UNSIGNED DEFAULT 0, PRIMARY KEY (course_id) ); CREATE TABLE enrollment ( stu_id CHAR(10) NOT NULL, course_id CHAR(6) NOT NULL, enroll_time DATETIME DEFAULT CURRENT_TIMESTAMP, grade DECIMAL(4,1) DEFAULT NULL, status ENUM(selected,dropped) DEFAULT selected, PRIMARY KEY (stu_id, course_id), CONSTRAINT fk_enr_stu FOREIGN KEY (stu_id) REFERENCES student(stu_id) ON DELETE CASCADE, CONSTRAINT fk_enr_cou FOREIGN KEY (course_id) REFERENCES course(course_id) );逻辑说明删除学生时级联删除其选课记录删除课程时不做级联用约束默认的RESTRICT行为阻止删除已经被选过的课程保护历史数据。字符集统一utf8mb4避免中文乱码和 emoji 导致的存储问题。capacity和selected_count用SMALLINT UNSIGNED就足够课程容量不可能超过 65535没必要给INT。3. 用存储过程与触发器实现学生选课系统的核心业务选课、退课、查余量3.1 选课存储过程把“查余量、判重复、写选课记录、扣名额”装进一个事务选课的核心逻辑是四个动作的串行组合。如果让应用层分四步执行任何一步失败都会留下半个业务状态。常见做法是写一个存储过程把所有动作放进一个事务边界。DELIMITER $$ CREATE PROCEDURE sp_enroll_course( IN p_stu_id CHAR(10), IN p_course_id CHAR(6) ) BEGIN DECLARE v_capacity INT; DECLARE v_selected INT; START TRANSACTION; SELECT capacity, selected_count INTO v_capacity, v_selected FROM course WHERE course_id p_course_id FOR UPDATE; IF v_selected v_capacity THEN ROLLBACK; SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 课程人数已满; END IF; IF EXISTS (SELECT 1 FROM enrollment WHERE stu_id p_stu_id AND course_id p_course_id AND status selected) THEN ROLLBACK; SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 不能重复选课; END IF; INSERT INTO enrollment(stu_id, course_id) VALUES (p_stu_id, p_course_id); UPDATE course SET selected_count selected_count 1 WHERE course_id p_course_id; COMMIT; END$$ DELIMITER ;逻辑说明FOR UPDATE是行级排他锁锁住课程表的这一行让两个并发会话不能同时读到同一个“剩余名额”。SIGNAL语句主动抛出异常并携带中文错误信息应用层直接捕获异常消息就能知道失败原因比返回一个错误码让前端猜更稳。参数说明p_stu_id和p_course_id是输入参数分别对应学生学号和课程号。这个存储过程没有输出参数业务是否成功通过异常判断调用方需要把调用包在try-catch里处理异常消息。3.2 触发器兜底直接 INSERT 也超不了选的容量保护存储过程能拦住按规矩走接口的人拦不住绕过存储过程直接执行INSERT INTO enrollment的操作。课程设计里经常出现这样的场景管理系统后台有一个“手动补录选课”的功能开发时图省事直接写了一条 INSERT结果容量限制完全失效。触发器能把这道防线沉到数据库引擎层。CREATE TRIGGER trg_enroll_before_insert BEFORE INSERT ON enrollment FOR EACH ROW BEGIN DECLARE v_selected INT; DECLARE v_capacity INT; SELECT selected_count, capacity INTO v_selected, v_capacity FROM course WHERE course_id NEW.course_id FOR UPDATE; IF v_selected v_capacity THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 选课人数已满触发器拦截; END IF; UPDATE course SET selected_count selected_count 1 WHERE course_id NEW.course_id; END逻辑说明NEW.course_id引用的是即将插入选课表的课程号。触发器先读课程人数超员就抛异常阻止插入未超员就自动维护selected_count。这样手动 INSERT 也被纳入容量控制。要注意的是普通 MySQL 触发器默认基于BEFORE或AFTER操作在BEFORE里抛异常对应的 INSERT 会整体失败。这里没有写FOR EACH ROW之外的分区条件FOR EACH ROW本身是 MySQL 触发器的固定语法每行操作都会执行。3.3 视图与索引三条高频查询路径的数据库 SQL 优化课程设计答辩时老师最喜欢问“你做了哪些查询优化”。至少有三条高频 SQL 需要保证性能学生查自己已选课程及成绩、学生查课程余量、教师查某门课选课名单。对应关系可以用一张表说清查询场景涉及表应命中的索引学生查成绩单enrollment course studentenrollment 复合主键(stu_id, course_id)查课程余量coursecourse 主键course_id教师查选课名单enrollmentenrollment 的course_id前缀索引enrollment的复合主键是(stu_id, course_id)按学生查成绩单时走的是联合索引最左前缀按课程查名单则不行需要额外建一个索引ALTER TABLE enrollment ADD INDEX idx_enr_course (course_id); CREATE VIEW v_student_score AS SELECT s.stu_id, s.stu_name, c.course_name, e.grade FROM enrollment e JOIN student s ON e.stu_id s.stu_id JOIN course c ON e.course_id c.course_id WHERE e.status selected; CREATE VIEW v_course_remain AS SELECT course_id, course_name, capacity - selected_count AS remain FROM course;逻辑说明视图不存储数据每次查询都会展开成底层 SQL 执行 JOIN。v_student_score把成绩单查询封装成语义清晰的接口应用层不必拼复杂的 JOIN。v_course_remain里capacity - selected_count是计算列直接暴露余量应用层把remain 0作为可选的判断条件。3.4 课程设计演示数据的最小增删改查集造数据不要手写上百条 INSERT先准备一个最小集合够展示功能就行INSERT INTO student VALUES (20240001, 赵一, M, 计算机学院, 2024, 2024-09-01), (20240002, 钱二, F, 软件学院, 2024, 2024-09-01), (20240003, 孙三, M, 计算机学院, 2024, 2024-09-01); INSERT INTO course VALUES (CS101, 数据库原理, 3.0, T001, 2, 0), (CS102, 操作系统, 3.5, T002, 30, 0); CALL sp_enroll_course(20240001, CS101); CALL sp_enroll_course(20240002, CS101); CALL sp_enroll_course(20240003, CS101);这里把CS101的容量设成 2第三个学生选课会触发存储过程里的满员异常演示效果最直观。三个学生两门课足够覆盖查成绩、查余量、满员选课失败、退课四个必演示场景。4. 从课程设计到答辩演示事务、权限与数据一致性验证4.1 并发超选的经典翻车现场为什么不能只写一条 INSERT如果课程设计里选课功能只有一句INSERT INTO enrollment演示时浏览器开两个窗口同时点选课就能复现数据不一致。MySQL 客户端开两个会话模拟两个学生选同一门只剩一个名额的课-- 会话 A START TRANSACTION; UPDATE course SET selected_count selected_count 1 WHERE course_id CS101; -- 会话 B在 A 提交前执行 UPDATE course SET selected_count selected_count 1 WHERE course_id CS101;会话 B 的 UPDATE 会阻塞等 A 提交后 B 才继续。这个过程中B 在不知道 A 已占名额的情况下可能已经向应用层返回了“选课中”的中间状态写入选课记录时又产生重复。正确做法是把“查余量、插选课记录、更新人数”放进同一个事务并且在一开始就SELECT ... FOR UPDATE锁住课程行。第三章的存储过程已经把这一步做好了这里重点是用两个会话把效果跑出来给答辩老师看。4.2 MySQL 用户与权限设计一套库同时服务学生、教师、管理员课程设计规范里要求“不同角色不同权限”这句话要落到数据库用户层面才有说服力。MySQL 里可以建两个用户对应学生和管理员两个角色CREATE USER stu_userlocalhost IDENTIFIED BY Stu2024; CREATE USER admin_userlocalhost IDENTIFIED BY Admin2024; GRANT SELECT, INSERT, UPDATE ON course_db.enrollment TO stu_userlocalhost; GRANT SELECT ON course_db.course TO stu_userlocalhost; GRANT ALL PRIVILEGES ON course_db.* TO admin_userlocalhost; FLUSH PRIVILEGES;权限矩阵可以整理成表格写进设计报告角色可操作表权限范围学生enrollmentSELECT / INSERT / UPDATE仅自己的记录学生courseSELECT管理员全部表所有权限教师enrollment / courseSELECT扩展场景“学生只能改自己的记录”在 MySQL 单靠表级授权不够GRANT无法指定行级条件。实际项目中通常通过视图加WHERE stu_id 当前登录用户实现行级过滤或者在应用层校验。课程设计里把账权限矩阵写清楚比硬编码实现更专业。4.3 事务回滚验证脚本演示用一段异常 SQL 证明原子性答辩现场只展示“正常选课成功”没有冲击力要主动表演一次回滚START TRANSACTION; INSERT INTO enrollment(stu_id, course_id) VALUES (20240001, CS102); UPDATE course SET selected_count selected_count 1 WHERE course_id CS102; -- 故意制造一个错误不存在的课程 INSERT INTO enrollment(stu_id, course_id) VALUES (20240001, XX999); ROLLBACK;执行到这个故意写错的课程XX999时外键约束会拒绝插入事务进入异常状态。如果不做ROLLBACK前面的选课记录虽然已经写入内存中的事务日志但不会真正落盘。执行完 ROLLBACK 后SELECT * FROM enrollment看不到CS102那条记录selected_count也没有变化这就是原子性的直观演示。需要补充的是MySQL 客户端默认autocommit1上面用显式START TRANSACTION手动关闭了自动提交所以 ROLLBACK 才有意义。如果不开事务直接跑三条 INSERT失败的那条会挡住后续语句但前面成功的语句已经永久提交清理起来更麻烦。5. 数据库课程设计答辩前的 5 个验证动作5.1 用 EXPI 证明索引真实生效不要只说“我建了索引”现场跑EXPLAIN SELECT * FROM enrollment WHERE course_id CS101;看执行计划里的key字段是不是idx_enr_coursetype是不是ref。如果key是 NULL说明索引没建上或者查询没走索引先ALTER TABLE enrollment ADD INDEX idx_enr_course(course_id);再跑一遍。5.2 用异常回滚证明事务不是摆设按 4.3 的脚本执行一次把回滚前后的SELECT COUNT(*) FROM enrollment结果截图对比。评审老师看到“异常后数据恢复原状”比什么口头说明都有效。5.3 用 mysqldump 命令准备一键恢复mysqldump -u root -p course_db course_db_backup.sql mysql -u root -p course_db course_db_backup.sql演示时先把库里的数据删两条再执行导入命令还原。注意恢复前要确认目标库存在否则执行导入会直接报错Unknown database。5.4 留一个扩展点选课日志表与连接池参数有余力的话加一张enrollment_log表记录每次选课/退课操作的客户端 IP 和操作时间用触发器写入。这是课程设计评分里很加分的扩展功能同时也是软件工程课程设计里审计模块的雏形。如果项目对接 Java 后端把连接池参数写进application.yml的 HikariCP 配置里maximum-pool-size调成 5 到 10演示时不会因为连接数爆炸卡死。答辩前把这五个动作依次过一遍每个结论都有 SQL 输出撑腰本数据库课程设计的数据侧支撑就完整了。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价