简介基于Python和MySQL实现的学生选课管理系统是一份面向计算机相关专业期末大作业或毕业设计的完整项目包。项目由导师指导并通过高分评审源码均经过本地编译调试可稳定运行适合需要快速构建课程设计或提升实战能力的学习者。压缩包共八个文件包含四个Python源码文件覆盖登录窗口、课程管理、教师管理等核心模块、一份数据库SQL脚本含表结构及示例数据、一份《数据库原理》课程报告文档、一个说明文件及配套配置文件整体大小约1.11MB结构清晰便于查阅。目前已有八十八人学习浏览具备一定参考价值。资源不仅提供了可直接运行的选课系统实现还附带了数据库设计思路与报告文档可帮助理解Python与MySQL的整合应用、界面与业务逻辑分离的编码方式以及常见调试排错经验是完成课程作业或入门管理信息系统开发的有力参考。1. 期末大作业的隐藏分水岭同样是“学生选课管理系统”为什么有人答辩轻松有人翻车期末答辩现场最怕的不是功能没写完而是本机跑得好好的演示时却连不上 MySQL。基于 python 和 mysql 的学生选课管理系统是绝大多数高校 Python 与数据库课程的期末大作业标配看起来就是学生、课程、选课三张表做增删改查但真正拉开分数的点很反直觉需求拆得是否克制、表结构是否诚实、选课流程里的数据一致性有没有兜住而不是功能贴了多长的菜单。这篇文章顺着这个标题把一个期末大作业该有的源码骨架、数据库脚本、报告写作思路和演示避坑一次讲透。适合正在赶大作业、准备期末答辩的新手也适合想把这个项目改造成毕设起点的老手。前几章讲怎么设计和实现后两章集中解决你答辩前一晚最容易遇到的几个玄学问题。2. 先把业务拆明白期末评分视角下的需求边界与选型理由2.1 大作业评分视角下的功能需求哪些功能必须出现哪些功能是给自己挖坑老师评阅这类项目翻代码时通常会先确认三件事有没有区分学生和管理员角色有没有完整的选课、退课闭环MySQL 是不是真的在业务里起作用而不是只拿来做了一个登录验证。按这个逻辑我一般把功能清单控制在六项登录按学号加密码、浏览课程列表、选课、退课、查看已选课程、按课程统计选课人数。这个清单已经能覆盖「增删改查 联表查询 事务」全部考点。不要在期末阶段往上加购物车、排课冲突检测、教师端排课管理这类功能。做得面面俱到看起来工作量很大但答辩时老师针对每个模块追问下去你很难全部讲透反而暴露实现粗糙的地方。这里有一个很多人忽略的点期末大作业的报告和源码同等重要。报告里对需求的描述决定了老师对你系统的第一印象。需求描述不要写「系统支持选课管理」这种空话写成「系统提供课程列表浏览学生可选选课人数未满的课程同一学生不可重复选择同一课程退课后课程名额自动回补」这样的可验证条目。每条需求对应一条测试路径老师照着走一遍整个项目就立住了。2.2 为什么是 Python MySQL与 SQLite、JSON 文件存储的实际差距很多同学问我期末作业用 SQLite 不也一样能跑吗确实能跑但评分观感完全不同。SQLite 是一个文件型数据库不需要安装服务端对学习数据库原理几乎没有帮助老师在报告里看不到你设计表结构、处理外键约束、排查连接问题的过程。MySQL 是教学和招聘要求里的常客你简历上写「熟练使用 MySQL」总得有一个项目是真的连过 MySQL 服务的。Python 这边的理由更直接。pymysql 库足够轻量不需要像 SQLAlchemy 那样引入 ORM 概念代码里写的就是原生 SQL正好和数据库课程的知识点一一对应。相比之下用纯 JSON 文件存数据虽然也能实现选课逻辑但并发场景下两个学生同时选同一门最后一门课文件读写根本没有事务和行锁的概念数据会静默出错这种系统拿去做大作业属于给自己埋雷。从踩坑风险看Python 加 MySQL 的主要成本在环境安装MySQL 服务端要装、密码要记、字符集要配。这个成本恰恰也是评分点因为报告里可以专门写一节「环境配置与问题排查」记录你从安装 MySQL 到跑通第一个查询的全过程。老师看重的往往不是你一次装成功而是你遇到问题后查资料、定位原因、解决掉的过程。2.3 数据模型设计学生、课程、选课三张表的字段取舍这个项目的核心数据模型就是三张表学生表 student、课程表 course、选课记录表 elective。不要在期末阶段强行拆出教师表、专业表、班级表关系拉长之后插入数据、写联表查询、画 ER 图的成本会翻倍而且容易出错。学生表我建议直接用学号做主键不要再用自增 id。学号本身就是业务上的唯一标识用它做主键报告里的 ER 图一画就懂查询 where 条件也直观。字段可以这样设计student_id 定长字符串name 姓名gender 性别major 专业class_name 班级password 密码字段。专业和班级直接冗余在学生表里期末项目里拆出单独的班级表没有实际收益。课程表的核心是 course_id、name、teacher、credit、capacity、selected_count。capacity 是课程容量selected_count 是已选人数。这里有一个设计取舍selected_count 是冗余字段因为理论上可以用「统计 elective 表里该课程的记录数」来得到已选人数不必单独存。但期末项目里我强烈建议保留这个字段理由有二一是选课时判断是否满员直接比较 selected_count 和 capacity一条条件更新语句就能完成不用先做子查询再更新二是报告里可以写「用冗余字段避免频繁聚合统计」老师会认为你考虑了性能。当然你要在报告里写清楚冗余字段需要在选课事务里同步自增、退课事务里同步自减这是用空间换一致性维护成本。选课记录表是业务核心字段要包含自增主键 id、student_id、course_id、select_time 选课时间以及一个可选的 score 成绩字段。选课记录表必须建唯一索引约束同一学生不能重复选同一门课这个约束要放在数据库层不能只靠 Python 代码先查询再判断。数据库层的约束是最终防线程序逻辑在并发场景下不可靠。自增主键 id 必须有因为如果拿 student_id 加 course_id 做联合主键后续想支持一个学生多次选修同一门课比如重修时表结构就锁死了。3. 建库到初始化MySQL 表结构 SQL 与测试数据的设计3.1 建库建表脚本完整 DDL 与字符集处理新建数据库和建表的脚本我习惯写成可重复执行的格式用 IF NOT EXISTS这样答辩前即使数据库被搞乱了重跑一遍 init.sql 就能救回来。完整的建库建表语句如下-- 创建数据库指定 utf8mb4 字符集避免中文乱码 CREATE DATABASE IF NOT EXISTS course_select_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE course_select_db; -- 学生表学号是天然主键 CREATE TABLE IF NOT EXISTS student ( student_id CHAR(10) NOT NULL COMMENT 学号, name VARCHAR(20) NOT NULL COMMENT 姓名, gender VARCHAR(2) DEFAULT 男 COMMENT 性别, major VARCHAR(50) COMMENT 专业, class_name VARCHAR(50) COMMENT 班级, password VARCHAR(64) DEFAULT 123456 COMMENT 登录密码演示用默认值, PRIMARY KEY (student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生表; -- 课程表selected_count 为冗余的已选人数 CREATE TABLE IF NOT EXISTS course ( course_id CHAR(8) NOT NULL COMMENT 课程编号, name VARCHAR(50) NOT NULL COMMENT 课程名称, teacher VARCHAR(20) COMMENT 授课教师, credit DECIMAL(2,1) DEFAULT 2.0 COMMENT 学分, capacity INT NOT NULL COMMENT 课程容量, selected_count INT DEFAULT 0 COMMENT 已选人数, PRIMARY KEY (course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程表; -- 选课记录表唯一索引防止重复选课 CREATE TABLE IF NOT EXISTS elective ( id INT AUTO_INCREMENT COMMENT 自增主键, student_id CHAR(10) NOT NULL COMMENT 学号, course_id CHAR(8) NOT NULL COMMENT 课程编号, select_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, score DECIMAL(4,1) DEFAULT NULL COMMENT 成绩可选, PRIMARY KEY (id), UNIQUE KEY uk_student_course (student_id, course_id), KEY idx_course_id (course_id), CONSTRAINT fk_elective_student FOREIGN KEY (student_id) REFERENCES student (student_id) ON DELETE CASCADE, CONSTRAINT fk_elective_course FOREIGN KEY (course_id) REFERENCES course (course_id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选课记录表;字符集这里要一次配全才能彻底避免中文乱码。数据库层面用 utf8mb4它是 utf8 的超集能存表情符号更重要的是和 MySQL 8.0 默认字符集对齐。排序规则用 utf8mb4_unicode_cici 表示大小写不敏感这样查询时不用纠结 python 和 Python 的拼写差异。Python 连接串后面也要配 charsetutf8mb4两层一致才靠谱。外键的级联策略我在脚本里做了一个区分学生删除了他的选课记录跟着删ON DELETE CASCADE课程被选过之后不允许直接删ON DELETE RESTRICT这样能保住历史选课数据报告的完整性也好写。课程表里 capacity 和 selected_count 都用了 INTcredit 用 DECIMAL(2,1)可以存 2.5 这种半分制学分。 password 字段期末项目直接用明文即可但报告里要补一句「生产环境应使用哈希存储」这是专业的体现。3.2 外键约束与唯一索引让数据库替你把关而不是靠程序 if 判断很多人写选课逻辑时习惯先查一遍有没有选过if 不存在就执行插入。这种做法在期末答辩演示时看不出问题但只要有两个窗口同时提交程序就会放行两次查询插进去两条重复记录。数据库层的唯一索引才是真正的兜底。上面的 DDL 里UNIQUE KEY uk_student_course 建立在 (student_id, course_id) 联合列上它的语义就是「同一个学生对同一门课最多只能有一条选课记录」。程序层面哪怕出了问题INSERT 语句一执行就会触发 1062 Duplicate entry 报错事务回滚数据不会脏。报告里写「利用唯一索引约束保证选课数据一致性」比写「程序里做了判断」高一个档次。还有一点容易被忽视MySQL 要求外键列上必须有索引。elective 表里我加了 KEY idx_course_id就是因为 course_id 作为外键虽然包含在联合唯一索引里但联合索引的最左前缀是 student_id单独拿 course_id 作为条件时走不了这个索引所以单独建一个索引给外键使用。这个细节在报告里写清楚很能体现数据库功底。课程删除用 RESTRICT 的问题也要在报告里讲一下课程表里有课程被选过后DELETE FROM course WHERE course_id... 会直接报外键约束错误老师看了会认为你理解了数据完整性。不用怕报错报错本身就是设计的一部分你要在报告里写明这是故意限制的目的是保留选课历史。3.3 初始化测试数据数据少而精把每个演示场景都铺好测试数据的质量直接决定演示效果我一般是「学生 6 到 8 人、课程 10 门左右」。课程数据要覆盖四种状态一门课容量 2 并且已选满用于演示选课失败一门课容量 3 已选 1 人用于演示选课成功一门课容量 30 但没人选用于演示排在末尾一门课容量 1用于演示最后一个名额被抢走的边界。没有这些数据你演示时想展示报错和异常分支都拿不出素材。-- 学生数据密码统一为 123456省得登录时记不住 INSERT INTO student (student_id, name, gender, major, class_name) VALUES (2024001, 张伟, 男, 计算机科学与技术, 计科2401), (2024002, 李娜, 女, 软件工程, 软工2401), (2024003, 王强, 男, 数据科学, 数据2401); -- 课程数据C001 已满C002 余量充足C003 无人选 INSERT INTO course (course_id, name, teacher, credit, capacity, selected_count) VALUES (C001, Python程序设计, 刘老师, 3.0, 2, 2), (C002, MySQL数据库, 陈老师, 2.5, 3, 1), (C003, 操作系统, 赵老师, 4.0, 30, 0); -- 选课记录张伟选了 C001 和 C002 INSERT INTO elective (student_id, course_id) VALUES (2024001, C001), (2024001, C002), (2024002, C001);INSERT 出来的数据要和上面的 DDL 对得上号C001 的 selected_count 是 2elective 表里正好有 2 条 C001 记录这个一致性在答辩时可以用一条联表查询当场验证老师会觉得你的数据不是随手造的。你可以用一条 SQL 检查冗余字段是否和数据一致SELECT c.course_id, c.name, c.selected_count, COUNT(e.id) AS real_count FROM course c LEFT JOIN elective e ON c.course_id e.course_id GROUP BY c.course_id, c.name, c.selected_count HAVING c.selected_count real_count;这条查询跑出来如果是空结果说明所有课程的已选人数冗余字段都和实际记录数一致。把它写进报告的测试章节是一个很漂亮的加分动作它证明你意识到了冗余字段可能导致的数据不一致并且设计了校验手段。LEFT JOIN 加 GROUP BY 加 HAVING三个考点一次用齐比单独写一条 SELECT 展示「我会用 JOIN」高级得多。4. 用 Python 把选课流程串起来pymysql 封装、事务与统计4.1 数据库连接封装把连接参数集中起来避免代码里到处写密码一个很常见的坏习惯是把数据库连接写在每个函数里host、user、password 散落七八处到最后改一个密码要全文替换。我一般会做一个 db.py 模块把连接配置集中管理核心代码如下# db.py import pymysql from pymysql.cursors import DictCursor DB_CONFIG { host: localhost, # MySQL 服务地址本机演示用 localhost port: 3306, # MySQL 默认端口 user: stu, # 建议用专用账号不要用 root password: stu_pass, # 专用账号密码 database: course_select_db, charset: utf8mb4, # 与建库字符集保持一致 cursorclass: DictCursor, # 查询结果返回字典按字段名取值 } def get_conn(): 获取一个数据库连接autocommit 默认关闭事务由调用方控制 return pymysql.connect( autocommitFalse, **DB_CONFIG )这段代码里有三个细节值得说。cursorclass 用 DictCursor查询结果每一行是字典比如 row[name] 直接按字段名取比元组下标 row[1] 可读性强很多视图代码里不用记字段顺序。autocommitFalse 是必须的选课和退课都是多语句事务如果每次 execute 都自动提交中途出错就没有回滚的机会。DB_CONFIG 字典用 ** 展开传入 connect后续要改连接参数只动这一处。password 字段建议用专用账号而不是 root原因后面第 5 章会专门说。这里先埋一个伏笔老师打开你的 db.py看到 root 密码裸写在代码里第一印象就打了折扣看到一个名为 stu 的专用账号权限只授给这个库他会认为你懂最小权限原则。4.2 选课核心逻辑先扣名额再插记录整个操作放一个事务里选课是这个系统的核心也是答辩的重点。最容易翻车的写法是先 SELECT 查剩余名额if 剩余名额大于 0 再 UPDATE 扣减再 INSERT 记录。这个流程在单用户操作时没有任何问题但两个学生同时提交时两个 SELECT 都读到剩余 1 个名额两个都执行 UPDATE最后把容量撑爆。正确做法是把「检查名额」和「扣减名额」合并成一条条件更新语句让数据库在行锁层面做判断# course_service.py import pymysql from db import get_conn def select_course(student_id: str, course_id: str) - str: conn get_conn() try: with conn.cursor() as cursor: # 条件更新仅当当前已选人数小于容量时才更新成功 sql UPDATE course SET selected_count selected_count 1 WHERE course_id %s AND selected_count capacity affected cursor.execute(sql, (course_id,)) if affected 0: return 选课失败课程不存在或容量已满 # 名额扣减成功后写入选课记录 insert_sql INSERT INTO elective (student_id, course_id) VALUES (%s, %s) cursor.execute(insert_sql, (student_id, course_id)) conn.commit() return 选课成功 except pymysql.IntegrityError: # 唯一索引冲突同一学生重复选同一门课 conn.rollback() return 选课失败你已选过这门课程 except Exception: conn.rollback() return 选课失败数据库异常操作已回滚 finally: conn.close()逻辑拆解一下UPDATE course SET selected_count selected_count 1 WHERE course_id %s AND selected_count capacity 这条语句是关键它把「查容量、判容量、扣名额」三个动作合并成一个原子操作。affected 0 说明课程不存在或者已经满员不需要再查一次。这里不抛异常用受影响行数判断是因为 UPDATE 没匹配到行是正常业务分支不是错误。affected 是 cursor.execute 的返回结果表示受影响行数这是 pymysql 等 DB-API 的标准行为。选课记录插入放在名额更新之后如果 INSERT 因为唯一索引冲突失败整个事务回滚前面 UPDATE 加上的名额也会被撤销数据回到一致状态。IntegrityError 是 pymysql 对 MySQL 1062 重复键错误的封装捕获它单独返回用户友好提示而不是把原始异常堆栈抛给界面。这里有一个容易忽略的边界条件万一某条课程记录的历史数据里 selected_count 本来就是错的比如比实际记录数大条件更新就永远更新不成功。所以初始化数据时做一次「对账」很重要第 3.3 末尾那条 HAVING 查询就是干这个的。数据有脏值业务逻辑再对也会出现查不出原因的现象。4.3 退课与统计查询事务回补名额联表统计交给 SQL 而不是 Python退课是选课的逆操作逻辑是删除 elective 里的记录再把 course 表的 selected_count 减一。这两个操作必须放在同一个事务里不然就会出现「课退了名额没回来」的经典翻车现场def drop_course(student_id: str, course_id: str) - str: conn get_conn() try: with conn.cursor() as cursor: # 先删除选课记录 delete_sql DELETE FROM elective WHERE student_id %s AND course_id %s affected cursor.execute(delete_sql, (student_id, course_id)) if affected 0: conn.rollback() return 退课失败没有找到选课记录 # 名额回补加一个大于 0 的保护避免误回补成负数 update_sql UPDATE course SET selected_count selected_count - 1 WHERE course_id %s AND selected_count 0 cursor.execute(update_sql, (course_id,)) conn.commit() return 退课成功 except Exception: conn.rollback() return 退课失败数据库异常操作已回滚 finally: conn.close()DELETE 的 affected 为 0 表示学生压根没选过这门课这个分支直接 rollback 然后返回提示。名额回补的 UPDATE 带上 selected_count 0 条件是一种防御性写法理论上如果数据一致删除记录后名额至少是 1这个条件永远不会失败但万一出现脏数据导致 selected_count 已经不正确这个条件能防止名额被减成负数。统计查询是期末报告里必须出现的一页。我建议直接用一条 JOIN 加 GROUP BY 的 SQL在数据库端完成统计不要在 Python 里循环计数。Python 做二次聚合既慢又容易出错而且体现不出你会 SQLdef list_course_stats(): conn get_conn() try: with conn.cursor() as cursor: sql SELECT c.course_id, c.name, c.teacher, c.capacity, c.selected_count, COUNT(e.id) AS real_count FROM course c LEFT JOIN elective e ON c.course_id e.course_id GROUP BY c.course_id, c.name, c.teacher, c.capacity, c.selected_count ORDER BY real_count DESC cursor.execute(sql) rows cursor.fetchall() for row in rows: print(f{row[course_id]} {row[name]} f已选 {row[selected_count]}/{row[capacity]} f实际记录 {row[real_count]}) return rows finally: conn.close()ORDER BY real_count DESC 就是热搜里常说的 mysql 排序在业务里的典型用法。COURSE 表用 LEFT JOIN 关联选课记录保证没有学生选的课程也会出现在结果里real_count 为 0这就不会丢数据。输出里同时打印 selected_count 和 real_count演示时你当场说一句「冗余的已选人数和实际记录数完全一致」就能证明初始化数据的正确性。MySQL 8.0 默认开启 ONLY_FULL_GROUP_BY所以 SELECT 里出现的非聚合列必须全部写进 GROUP BY。上面 SQL 里 c.course_id、c.name、c.teacher、c.capacity、c.selected_count 都进了 GROUP BY如果你漏掉任何一个执行会直接报错。这是初学者写 GROUP BY 最高频的报错放到报告排错记录里也是一条素材。5. 期末答辩避坑检查单演示现场最容易翻车的 6 个问题5.1 中文乱码从建库到连接三层字符集设置缺一层就乱现象MySQL 客户端里插入的中文正常Python 查出来后变成问号或者 PyCharm 控制台输出中文正常写进数据库再查出来就是乱码。原因字符集配置只做了一部分。建库时指定了 utf8mb4但 Python 连接串没带 charset或者建表语句没指定字符集继承的还是库级别默认值还有一种情况是连接串写了 utf8而数据库是 utf8mb4两者不一致。解决把三层全检查一遍。第一层建库建表时用 DEFAULT CHARACTER SET utf8mb4表结构创建后可以用 SHOW CREATE TABLE course 确认第二层pymysql 连接参数里写 charsetutf8mb4第三层Python 源码文件本身要保证是 UTF-8 编码PyCharm 右下角或文件头检查一下编码声明。三个字符集问题排查完还乱码就别再改代码了直接查 MySQL 系统变量SELECT character_set_database 看库的当前值。5.2 MySQL 8.0 认证插件导致的连接失败pymysql 报错与处理现象MySQL 8.0 环境里Python 程序一执行 connect 就抛异常报错信息类似 Authentication plugin caching_sha2_password cannot be loaded。原因MySQL 8.0 默认认证插件是 caching_sha2_password而某些版本或编译方式的 pymysql 对它支持不完整。这个报错几乎只在 MySQL 8.0 上出现老版本 MySQL 用的是 mysql_native_password不存在这个问题。解决不要尝试去升级或更换驱动最可靠的做法是创建一个使用旧认证插件的专用账号。执行 SQLCREATE USER stulocalhost IDENTIFIED WITH mysql_native_password BY stu_pass; 然后 GRANT ALL PRIVILEGES ON course_select_db.* TO stulocalhost; 程序里改用这个账号连接。这个操作既解决了认证问题又顺便把 root 账号从代码里替换掉了一举两得。注意老师机器上可能是 MySQL 5.7 也可能是 8.0用 mysql_native_password 的账号在两种版本下都能连兼容性最好。5.3 退课后名额没回补典型的事务边界错误现象退课操作提示成功选课记录也没了但课程列表里已选人数没减少另一门容量很小的课依然显示满员。原因DELETE 和 UPDATE 放在了两个事务里。很多人写退课时先执行 DELETEcommit再执行 UPDATE 回补名额第二个操作失败时第一个已经提交了名额就永久少了。还有一种情况是两个操作在同一个事务但代码里漏了 commit两个都没生效。解决退课必须是一个事务DELETE 和 UPDATE 之间不执行 commit先删记录再回补名额两步都成功才统一 commit任一步抛异常就 rollback。代码结构和上面 4.3 给出的函数完全一致。排查时看程序逻辑更快的办法是手动在 MySQL 里对这个同学重新执行一遍 DELETE 加 UPDATE观察 affected 行数确认表结构和数据本身没问题问题就锁定在代码事务边界上。5.4 报告里的 ER 图与代码不一致答辩时被追问最多的点现象报告里的 ER 图画的是四个表代码里只有三个报告里写了 elective 表用联合主键代码里却是自增 id报告里字段叫 stu_name数据库里列名是 name。老师对照着报告看代码前后对不上整个项目的可信度立刻崩塌。原因报告先写完代码后改动改完代码没有回头更新报告。这是期末项目最常见的问题没有之一。解决交付前最后一天对着数据库导出的实际结构改报告。用 SHOW CREATE TABLE elective 拿到真实 DDL照着画 ER 图、列字段用 SELECT * FROM elective LIMIT 5 核对测试数据。如果文档和代码不一致宁可花半小时改报告也绝不要临时改代码去迁就文档因为改代码会引入新的运行风险离答辩只剩一晚时不值得冒险。5.5 换电脑跑不起来路径、依赖和 MySQL 服务的三连坑现象在自己电脑上好好的到演示机器上双击运行直接抛 ModuleNotFoundError 或者连接拒绝。学生借了老师的投影电脑插上 U 盘开始演示开场就卡住。原因三种情况叠加。一是依赖没导出对方机器没装 pymysql二是连接配置写死了绝对路径或者特殊密码三是 MySQL 服务没启动或者对方机器 MySQL 的 root 密码跟你代码里写的不一样。解决项目根目录放一个 requirements.txt内容写 pymysql代码里连接参数全部收敛到 db.py 的 DB_CONFIG演示前先问清楚对方机器 MySQL 的版本和密码不行就在答辩现场用自己电脑演示。更专业的做法是带一条初始化命令在演示机器上先执行 mysql 客户端导入 init.sql再跑程序整个过程控制在两分钟内。血泪经验答辩前把整个流程在另一台电脑上完整走一遍装依赖、启服务、导数据、跑功能这一遍能暴露 80% 的现场问题。5.6 root 密码裸奔与最小权限意识老师一眼就能看出的不专业现象db.py 里直接用 root 连数据库密码明文写着 123456甚至 root 没有密码。代码能跑但老师翻到这一页时眉头一皱。原因很多初学者只求能连通没想过数据库账号的权限边界。root 是超级管理员它不应该出现在业务程序的代码里这是数据库安全的基本常识。期末项目虽然不涉及生产环境但老师会拿行业标准来评价你的代码习惯。解决创建一个专用账号只授予当前项目的库权限。SQL 如下先用 root 登录 MySQL 执行 CREATE USER stulocalhost IDENTIFIED BY stu_pass; 再执行 GRANT ALL PRIVILEGES ON course_select_db.* TO stulocalhost; 最后 FLUSH PRIVILEGES。程序连接串里把 user 和 password 换成这个专用账号。这不会花超过五分钟但报告里能写一条「数据库连接使用最小权限账号避免业务连接使用管理员账户」专业性一下就凸显了。6. 答辩演示的验证路径从一条 SQL 到一键启动脚本再到一份拿得出手的测试记录答辩前一天的验证不要从头到尾点一遍菜单就算完那样记不住结果老师一问数据就说不上来。我习惯把验证做成一串可重复执行的清单每一条都有预期结果正常选课返回成功重复选同一门课返回「已选过」选满员课程返回「容量已满」退课后再选同一门课能成功统计列表的已选人数和 elective 实际记录数一致。五条跑完核心业务闭环就验证完了。为了节省演示时间我会写一个一键启动脚本它的职责不是启动 GUI而是做环境自检检查 pymysql 是否安装、连接 MySQL 是否成功、三张表是否存在、测试数据是否齐全。如果表不存在就自动执行 init.sql 重建并且初始化脚本要做成幂等的重复运行不会产生重复数据。原理是用 DROP TABLE IF EXISTS 重建表结构再重新插入测试数据。演示现场万一数据库出了状况重跑一遍就能恢复到干净状态这手救场设计能保证你的演示不会在中途卡死。还有一个加分技巧是 EXPLAIN 验证索引。演示时在 MySQL 里跑一句 EXPLAIN SELECT * FROM elective WHERE student_id2024001; 展示结果是走了 uk_student_course 这个联合索引type 是 ref。解释一句「联合索引的最左前缀匹配规则所以按学号查选课记录能走索引」老师通常会对这个细节刮目相看。这不是让你临时背概念而是把你自己设计的表结构真正说清楚。最后说一个我的教训毕业设计答辩前两天我改了 elective 表结构忘了更新报告里的 ER 图答辩时老师三分钟就指出了不一致那一段对话成为整场答辩最尴尬的环节。从那之后我养成了习惯任何项目交付前用 SHOW CREATE TABLE 导出真实表结构再对着它写文档、画图、准备讲稿。代码诚实文档才敢诚实。这个习惯帮我省下了很多事后补救的麻烦希望帮到你。本文还有配套的精品资源点击获取