ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python与MySQL学生选课管理系统:数据库设计到实现与答辩

Python与MySQL学生选课管理系统:数据库设计到实现与答辩 简介面向计算机专业课程设计与期末大作业场景该基于Python和MySQL的学生选课管理系统提供完整可运行的源码、数据库脚本与课程报告。项目结构清晰适合正在完成选课、信息管理类课题的本科生参考或二次开发。压缩包共8个文件包括4个Python源码文件、1个SQL建表脚本、1份《数据库原理》报告文档、1个Markdown说明文件及辅助配置文件整体仅1.11MB轻量易用。其中登录窗口模块负责界面交互教师端模块处理业务逻辑配合数据库脚本可快速搭建基础环境。目前已有88人学习下载。配套报告经导师指导并获得高分评价源代码经本地编译调试可稳定运行从界面登录、选课逻辑到数据库表设计均有覆盖便于理解Python与MySQL的联动开发也可直接提交作业或作为课设参考。1. 学生选课管理系统期末大作业的及格线还是分水岭拿到「基于 Python 和 MySQL 的学生选课管理系统」这个期末大作业题目时多数人的第一反应是登录、选课、退课、查成绩跑通就能交差。但打开评分标准会发现源码、数据库、报告三件事的权重几乎一样真正拉开差距的不是界面多好看而是数据库表设计得是否合理、代码有没有分层、报告里有没有讲清楚设计理由。我见过不少同学花三天把功能写完却在答辩时被老师一句「为什么选课表要联合主键」问住。这篇文章按「能直接交且拿得出手」的标准来写从表结构、PyMySQL 主干代码到报告结构和答辩话术拆开讲适合正在准备这个题目的在校生也适合想借一个完整小项目把 Python 连 MySQL 的套路理顺的开发者。2. 先想清楚再写码系统的角色、流程与模块边界这个题目叫「管理系统」本质上是三类人围绕课程数据做增删改查。很多同学跳过分析直接写代码写到一半才发现「教师也要开课」这个需求没地方放回头再改表结构改动成本翻倍。所以第一步不是建工程而是把角色、功能和数据流在纸上过一遍。2.1 角色建模学生、教师、管理员三类身份分别要什么学生端的核心诉求是浏览可选课程、选课、退课、查看已选课程和成绩。教师端的核心诉求是维护自己开设的课程、查看选课名单、录入成绩。管理员端的核心诉求是管理学生账号、教师账号、课程信息以及处理学期切换时的数据初始化。用一个表把三类角色和功能点列出来报告里「需求分析」一章可以直接照着展开角色核心功能数据操作学生浏览课程、选课、退课读course、增删选课记录教师开课、看名单、录成绩增改course、改成绩字段管理员账号管理、课程管理增删改student/teacher/course注意一个容易漏掉的需求成绩是教师录入的但成绩最终挂在「学生-课程」的关联上所以选课记录表里必须预留成绩字段的扩展位置否则后边加需求时又要改表。2.2 模块边界界面、业务逻辑、数据访问三层分离学生大作业最常见的写法是把所有代码塞进一个文件点击按钮就直接拼 SQL登录逻辑、选课逻辑、数据库连接全部搅在一起。这种代码跑通没问题但报告里只能贴代码没法讲架构答辩被问「如果加一个退课功能你改哪里」就答不上来。我一般会建议按三层组织代码。数据访问层单独一个文件封装所有 SQL只负责执行和返回结果业务逻辑层处理「选课前判断容量」「退课前判断是否已选」这类规则界面层只做输入输出和按钮事件不直接操作数据库。下面是一个模块划分的示意不贴完整代码先讲清楚调用关系# db_utils.py — 数据访问层所有 SQL 都在这层 def get_connection(): pass def query_one(sql, args): pass # service.py — 业务逻辑层处理规则 def select_course(student_id, course_id): pass def drop_course(student_id, course_id): pass # main_ui.py — 表现层只负责交互 def login_page(): pass def student_menu(): pass这样分层有三个直接好处第一出问题时能快速定位是 SQL 写错还是规则判断写错第二报告里可以画一张三层架构图内容立刻扎实第三答辩时如果老师问「改成 Web 版怎么办」可以直接答「只替换表现层业务逻辑和数据访问不动」这是很加分的回答。2.3 技术选型Tkinter 还是控制台PyMySQL 还是官方驱动界面方案通常有四类纯控制台、Tkinter、PyQt5、Flask 网页。控制台最省事但界面简陋期末大作业难免显得单薄Flask 能做网页版但要处理请求路由、模板、跨域工作量翻倍对「以数据库和 Python 基本功为考核重点」的课程设计来说性价比偏低PyQt5 界面现代但打包和依赖配置对新手很不友好。我的建议是如果指导老师没有明确要求网页版优先选 Tkinter。它是 Python 标准库自带的不需要额外安装不会出现「在我电脑上能跑到机房一 import 就报错」的尴尬。控制台版本可以当中文编码问题排查的备用方案。数据库驱动方面PyMySQL 是社区用得最多的方案纯 Python 实现文档多报错信息容易检索到mysql-connector-python 是官方驱动但某些版本对 Python 3.10 的兼容处理反而让新手困惑。选 PyMySQL 基本不会走弯路。3. 数据库是这门大作业的得分大头表结构设计、外键与约束数据库设计是本项目回报率最高的部分。程序功能大家都做得差不多但表设计是否规范、约束是否合理、有没有用上事务和视图在报告和答辩里高下立判。这一章把建库建表的 SQL 完整给出每一步都说明理由。3.1 学生表、教师表、课程表三张基础表的字段设计先建库再建基础表。注意编码必须显式指定为 utf8mb4这是后面所有中文乱码问题的根源性设置。CREATE DATABASE IF NOT EXISTS course_selection DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE course_selection; CREATE TABLE student ( student_id CHAR(9) PRIMARY KEY COMMENT 学号固定9位, name VARCHAR(50) NOT NULL COMMENT 学生姓名, password_hash VARCHAR(255) NOT NULL COMMENT 密码哈希值不存明文, major VARCHAR(50) DEFAULT COMMENT 专业, enrolled_year INT DEFAULT NULL COMMENT 入学年份用于年级筛选 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE teacher ( teacher_id CHAR(6) PRIMARY KEY COMMENT 教师工号, name VARCHAR(50) NOT NULL COMMENT 教师姓名, department VARCHAR(50) DEFAULT COMMENT 所属院系, title VARCHAR(20) DEFAULT COMMENT 职称 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( course_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 课程序号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, teacher_id CHAR(6) NOT NULL COMMENT 授课教师, capacity INT NOT NULL DEFAULT 30 COMMENT 容量上限, selected_count INT NOT NULL DEFAULT 0 COMMENT 当前已选人数冗余字段, credit DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT 学分, schedule VARCHAR(100) DEFAULT COMMENT 上课时间地点描述, FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个选型理由要在报告里写出来学号用 CHAR(9) 而不是 INT是因为学号是标识符不是数值可能含前缀 0用数字类型会丢失格式密码字段存 password_hash 而不是明文哪怕作业要求不高这个设计也能体现出安全意识course 表里的 selected_count 是冗余字段是为了选课时直接读它判断容量避免每次 COUNT 选课表导致慢查询这在报告「数据库优化」部分是一个亮点。注意 ENGINE 统一用 InnoDB。如果使用 MyISAM外键约束会失效事务也不会真正生效后面 Python 端做选课事务就失去意义了。3.2 选课记录表联合主键是必须的别只给自增 id选课记录是学生和课程的多对多关系。最稳妥的设计是联合主键 (student_id, course_id, semester)把「一个学生在一个学期里不能重复选同一门课」直接交给数据库保证。CREATE TABLE course_selection_record ( student_id CHAR(9) NOT NULL COMMENT 学号, course_id INT NOT NULL COMMENT 课程号, semester VARCHAR(20) NOT NULL COMMENT 学期如 2024-2025-1, selection_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, score DECIMAL(5,1) DEFAULT NULL COMMENT 成绩教师录入, PRIMARY KEY (student_id, course_id, semester), KEY idx_student_semester (student_id, semester), KEY idx_course_semester (course_id, semester), FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么不只用自增 id 做主键因为自增 id 只能保证物理唯一不能保证逻辑唯一——同一个学生在同一学期选同一门课理论上不被允许如果主键是自增 id数据库层面就无法拦截这条重复数据只能依赖 Python 代码先查再插而先查再插在并发或双击提交时必然有漏洞。联合主键把这个规则下推到数据库层无论程序怎么调重复数据都插不进去。这个设计还能直接对付「学生双击提交按钮」这种真实场景当第二次 INSERT 触发主键冲突时Python 端捕获到 DuplicateEntry 就知道当前会话已经选过这门课不需要再写繁琐的预查询。3.3 触发器与存储过程适当展示但别让自己下不了台为了在报告里体现数据库能力可以加一个触发器选课人数超过容量时拒绝插入。常见的做法是在课程表上维护一个已选人数的触发器或者靠 Python 端事务控制。但说实话在期末大作业里触发器容易出现「答辩现场一导入就报错」的翻车事故因为不同版本的 MySQL 对触发器权限和语法严格程度不一样。我一般会建议把触发器或存储过程的 SQL 写进报告但生产代码里用 Python 事务实现同样的逻辑这样既不丢加分项也不给自己留风险。下面是一个在视觉上有冲击力、又不会太复杂的存储过程示例DELIMITER // CREATE PROCEDURE sp_select_course( IN p_student_id CHAR(9), IN p_course_id INT, IN p_semester VARCHAR(20) ) BEGIN DECLARE current_selected INT; DECLARE course_capacity INT; SELECT capacity, selected_count INTO course_capacity, current_selected FROM course WHERE course_id p_course_id FOR UPDATE; IF current_selected course_capacity THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 课程容量已满; END IF; INSERT INTO course_selection_record(student_id, course_id, semester) VALUES (p_student_id, p_course_id, p_semester); UPDATE course SET selected_count selected_count 1 WHERE course_id p_course_id; END// DELIMITER ;这个存储过程展示了事务、行锁、条件判断和错误抛出四个知识点报告里写一段「数据库端选课过程设计」就十分充实。但提醒一句数据库用户必须有 CREATE ROUTINE 权限才能创建机房或虚拟机环境如果没有这个权限这条 SQL 会执行失败所以它更适合写在报告「高级功能设计」一节而不是作为系统运行必需。3.4 初始化数据测试数据要造得像真的一样课程设计评分时老师大概率会翻看数据库里的数据是否合理。只插入「test1 / test2」这类数据会显得敷衍建议用贴近真实场景的数据初始化。INSERT INTO teacher (teacher_id, name, department, title) VALUES (T001, 刘老师, 计算机学院, 副教授), (T002, 陈老师, 软件工程学院, 讲师); INSERT INTO course (course_name, teacher_id, capacity, selected_count, credit, schedule) VALUES (Python程序设计, T001, 60, 2, 3.0, 周一 3-4节 教学楼A101), (数据库原理, T001, 50, 3, 3.5, 周二 1-2节 教学楼B201), (操作系统, T002, 40, 1, 3.0, 周三 5-6节 教学楼C301); INSERT INTO student (student_id, name, password_hash, major, enrolled_year) VALUES (202300101, 张同学, SHA2(123456, 256), 软件工程, 2023), (202300102, 李同学, SHA2(123456, 256), 计算机科学, 2023);注意密码尽量用 SHA2 而不是 MD5。MD5 已经明确不安全虽然很多老课设范例还在用但答辩时一旦被问「用 MD5 有什么问题」会很难收场。用 SHA2(123456, 256) 既展示了对散列算法的了解也避免存储明文密码被质疑。4. Python 连接 MySQL 的主干实现登录、选课、查成绩模块这一章给出可以直接抄的代码。核心思路不变数据访问层统一封装业务逻辑层处理规则界面层只负责调用。4.1 连接对象封装参数、游标和统一关闭先写一个 db_utils.py集中管理数据库连接。这样做的好处是换库、换用户名时只改一个文件。import pymysql from pymysql.cursors import DictCursor DB_CONFIG { host: 127.0.0.1, port: 3306, user: root, password: 你的密码, database: course_selection, charset: utf8mb4, cursorclass: DictCursor, autocommit: False, } def get_connection(): 每次业务操作获取一个新连接用完立即关闭避免长时间占用 return pymysql.connect(**DB_CONFIG)DB_CONFIG 里的关键参数有三个charset 必须与建库时的 utf8mb4 保持一致否则中文写入会出错autocommit 设为 False 是必须的因为选课操作要作为事务提交如果开了自动提交前面选课的回滚逻辑就白写了cursorclass 用 DictCursor让查询结果以字典形式返回代码里直接写 row[course_name]比写 row[2] 可读性高得多。4.2 登录与角色路由参数化查询杜绝字符串拼接登录模块是门面也是最容易出 SQL 注入的地方。无论用户输入什么都拼进 SQL在小项目里貌似没事但在作业里被老师盯上轻则扣分重则被认定为「没有安全意识」。def login(username, password): 统一的登录入口返回 (role, user_id) 或 None conn get_connection() try: with conn.cursor() as cur: # 先用参数化查询查学生表再查教师表命中哪个就是哪个角色 cur.execute( SELECT student_id AS uid, name, password_hash FROM student WHERE student_id %s, (username,), ) stu cur.fetchone() if stu: if stu[password_hash] sha256(password): return student, stu[uid] return None, 密码错误 cur.execute( SELECT teacher_id AS uid, name, password_hash FROM teacher WHERE teacher_id %s, (username,), ) tea cur.fetchone() if tea and tea[password_hash] sha256(password): return teacher, tea[uid] return None, 账号或密码错误 finally: conn.close()注意%s占位符是参数化查询用户输入永远作为参数传给 SQL不会被解释成 SQL 代码。这是唯一推荐的写 SQL 的方式。把sha256(password)封装成一个公共函数登录、改密码、初始化时都用它不要到处重复实现散列逻辑。4.3 选课事务先查容量再插入失败必须回滚选课是本系统里唯一有「资源竞争」的业务。两个学生同时选同一门课如果每人都在 Python 里先 SELECT 再 INSERT那么可能两个人都查到剩余名额然后都插入成功课程超员。在数据库层解决这个问题的方式是行锁。先用SELECT ... FOR UPDATE把课程记录锁住再判断容量再插入选课记录并更新已选人数最后统一提交。这样可以保证同一时刻只有一个事务能读到 count 值。import pymysql from db_utils import get_connection def select_course(student_id, course_id, semester): conn get_connection() try: with conn.cursor() as cur: # 锁住课程行防止并发超卖 cur.execute( SELECT capacity, selected_count FROM course WHERE course_id %s FOR UPDATE, (course_id,), ) course cur.fetchone() if course is None: raise ValueError(课程不存在) if course[selected_count] course[capacity]: raise ValueError(课程容量已满) # 联合主键兜底重复选课 try: cur.execute( INSERT INTO course_selection_record (student_id, course_id, semester) VALUES (%s, %s, %s), (student_id, course_id, semester), ) except pymysql.err.IntegrityError as e: # DuplicateEntry 说明该生这学期已经选过 raise ValueError(你已经选过这门课程) from e cur.execute( UPDATE course SET selected_count selected_count 1 WHERE course_id %s, (course_id,), ) conn.commit() return True, 选课成功 except ValueError as e: conn.rollback() return False, str(e) except pymysql.err.Error as e: conn.rollback() return False, f数据库错误: {e} finally: conn.close()这段代码有三个细节值得在报告里专门讲第一FOR UPDATE是行级锁锁的是 course 表里这条课程记录事务提交或回滚后释放第二IntegrityError的捕获利用的是联合主键的约束不管前面有没有查重数据库都会挡下重复数据第三rollback 和 close 都放在 finally 或异常分支里保证连接一定关闭。退课操作是选课的逆向删除选课记录同时把 course 表的 selected_count 减一。逻辑类似注意也必须事务化否则会出现「记录删了人数没减」的不一致状态。4.4 成绩查询与统计聚合让数据库干别用 Python 循环查成绩这个功能很简单但常见误用是查出所有选课记录然后在 Python 里用循环算平均分、算最高分。数据量小时没感觉但完全绕过了 SQL 的能力答辩时也很难自圆其说。SELECT c.course_name, cr.score, ROUND(AVG(cr.score) OVER (PARTITION BY cr.course_id), 2) AS course_avg FROM course_selection_record cr JOIN course c ON cr.course_id c.course_id WHERE cr.student_id %s;这里用了窗口函数虽然这是个稍微进阶的语法但更能展示数据库功底。如果担心导出的 MySQL 版本太低退一步用普通的 GROUP BY 配合子查询也能实现同样效果。教师端录成绩则是一次 UPDATEdef update_score(teacher_id, course_id, student_id, score): conn get_connection() try: with conn.cursor() as cur: # 确保该课程确实属于这位教师防止越权录成绩 cur.execute( UPDATE course_selection_record cr JOIN course c ON cr.course_id c.course_id SET cr.score %s WHERE cr.course_id %s AND cr.student_id %s AND c.teacher_id %s, (score, course_id, student_id, teacher_id), ) if cur.rowcount 0: raise ValueError(没有找到匹配的选课记录) conn.commit() return True, 成绩录入成功 except ValueError as e: conn.rollback() return False, str(e) finally: conn.close()注意cur.rowcount 0的意义UPDATE 没有匹配到行说明要么选课记录不存在要么课程不属于当前教师。这是给业务逻辑层返回错误信号的一种标准做法避免「 UPDATE 成功但影响行数为 0」这种难以察觉的静默失败。4.5 管理员端通用 CRUD用字典拼接也不要在 SQL 里拼参数管理员的账号管理、课程管理本质是对三张基础表的增删改查。为了控制篇幅不写五个一模一样的函数而是用一个通用方法处理常见更新def admin_update(table, field, value, condition_field, condition_value): 通用更新仅供管理员模块调用注意 table 和 field 不允许用户输入 conn get_connection() try: with conn.cursor() as cur: sql fUPDATE {table} SET {field} %s WHERE {condition_field} %s cur.execute(sql, (value, condition_value)) conn.commit() return True, 更新成功 except pymysql.err.Error as e: conn.rollback() return False, f数据库错误: {e} finally: conn.close()必须强调这种动态表名和字段名的拼接方法只适用于管理员选单里预设好的值绝对不能让用户输入直接进入 table 或 field那样等于把 SQL 注入大门打开。程序里要把允许更新的表名白名单写死字段名用映射表转一下。5. 避坑指南期末大作业最常见的五个翻车现场期末大作业的翻车点高度集中几乎每年都有人栽在同一批问题上。这一章写五个最常见、最致命的坑每条按现象、原因、解决三步讲清楚。5.1 中文乱码建库、连接、表三处全部一致才能根治现象控制台输出「鍙戦€€」一类乱码或者 Navicat 里看表注释是问号页面上显示中文名变成「???」。原因建库时用了默认 latin1 编码或者连接串没写 charset 参数或者表本身不是 utf8mb4。三个环节只要一处不一致中文就会在写入或读取时丢失。解决建库 SQL 里显式写DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci连接 DB_CONFIG 里写charset: utf8mb4如果项目已经在跑MySQL 8 下可以执行ALTER DATABASE course_selection CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;补救。Windows 控制台里还需要在运行前执行chcp 65001否则 Python 的 print 输出到控制台仍会乱码。5.2 外键删不掉误建了物理外键导致选课记录清不掉现象管理员想清空选课记录重新初始化执行DELETE FROM course_selection_record;时报错「Cannot delete or update a parent row」但表里明明没有数据。原因course 表与选课记录表的外键约束在生效只要 course 表里存在课程就不能随意删选课记录实际上更多情况是反过来——课程表被引用所以课程不能直接删除。很多同学把外键建得很随意没想清楚引用关系。解决删除顺序必须遵循孩子先于父亲。先删选课记录再删课程最后删学生或者在建外键时显式加ON DELETE CASCADE这样删除课程时自动清掉关联选课记录。但 CASCADE 会掩盖误操作风险建议作业里保留外键不依赖 CASCADE在代码里按正确顺序执行删除并把这条设计写进报告。5.3 pymysql 报 Lost connection不是玄学是 wait_timeout现象程序刚启动时一切正常放着喝杯水回来点选课直接报「pymysql.err.OperationalError: Lost connection to MySQL server during query」。原因MySQL 默认 wait_timeout 是 8 小时但数据库连接如果空闲太久或被服务器端断开Python 端持有的连接对象就失效了。这是长期保持全局连接没用连接池的必然结果。解决采用每笔业务操作新建连接、用完关闭的模式就是前面对生成点 get_connection 的方式。这样每次操作前都是新连接不会用过期连接。如果想进一步省事可以在连接参数里加init_commandSET wait_timeout28800但这不是根治只缓解了服务端掐断的时间点。5.4 双击提交导致重复选课联合主键兜底界面层也要做保护现象选课页面点「提交」按钮后网络卡顿或界面无响应学生又点了一次结果数据库里出现两条同样的选课记录。原因代码里没做主键防重也没做按钮防抖或者按钮在事务提交前被再次点击。解决第一道防线是联合主键数据库直接拒掉重复插入前面 4.3 节已经讲过了第二道防线是在界面层给按钮增加一个状态标志点击后立即btn.config(statedisabled)事务结束后再恢复。如果界面层用的是控制台版本可以在函数入口加一个模块级变量is_processing函数执行期间置 True二次调用直接返回提示。5.5 报告里全是代码截图答辩被连环问现象报告写了 30 页其中 20 页是源码截图老师的提问集中在「表关系是什么」「为什么这样设计」学生答不上来。原因把工作量理解为代码量。实际上代码可以短但设计理由必须充分。解决报告的结构按照「需求分析 → 数据库设计 → 功能实现 → 测试与分析」来组织。数据库设计部分必须有 E-R 图、字段说明表、关系说明功能实现部分只截关键代码段并配文字解释。这一点在下一章展开。6. 报告与答辩剩下的 5000 字工作量怎么写得让导师觉得你做了三天功能代码可能两三天就写完了但报告是决定成绩下限的地方。很多同学不知道报告真正应该写什么导致内容要么是代码堆砌要么是空洞的「本系统使用 Python 开发具有易用性、稳定性」。这一章给出一个可复用的报告结构和答辩话术。6.1 报告结构E-R 图、用例图、数据库设计说明各占多少按一份 20 页左右的期末大作业报告计算各部分篇幅可以这样分配报告章节内容要点大约篇幅需求分析角色功能表、业务流程描述3 页总体设计三层架构图、用例图、模块划分4 页数据库设计E-R 图、建表语句、字段说明、外键约束理由6 页功能实现登录、选课、退课、成绩录入的关键代码与界面截图4 页测试与分析测试用例、中文乱码处理、异常场景3 页注意数据库设计部分占最大篇幅。每一张表都要配一个字段说明表格字段名、类型、约束、含义写清楚。老师翻开报告第一眼看的是有没有 E-R 图第二眼看的是建表语句里有没有外键和主键策略第三眼看的是关键业务有没有事务处理。这三点做到了报告就已经超出平均水准。代码截图只截核心逻辑段不要整页贴源码。界面截图要标注「图 4-1 学生选课界面」这类编号配合一两句功能说明。不要试图在报告里证明你写了多少行代码——「为什么这样设计」的价值远高于「写了多少行」。6.2 答辩预演先讲业务规则再讲约束最后讲踩坑答辩常见的时间安排是每人 5 分钟。我习惯让学生按这个节奏练前 30 秒讲项目背景与整体功能中间 60 秒集中在数据库设计上说明学生、课程、选课记录三张表之间的关系强调联合主键和外键的作用剩下 60 秒讲一个亮点比如选课事务里用到了FOR UPDATE行锁以及遇到并发超卖如何解决最后留一点时间讲踩过的中文乱码坑和你如何定位修复。老师最爱追问的「为什么」主要集中在三个地方学号为什么不用数字类型、选课记录表为什么不用自增主键、容量字段为什么要冗余。回答方向在本章 3.1 和 3.2 已经写透了。只要能把这三件事讲清楚对答就会很顺。讲讲我自己的习惯每次带学生做这类系统都会要求把建表 SQL 单独存成一个 init.sql 文件并且在报告的附录里附上「环境说明」写清 Python 版本、PyMySQL 版本、MySQL 版本以及如何从零初始化数据库。这样无论是自己换电脑演示还是老师拿到源码后要跑一遍都能在 10 分钟内恢复出环境。少了这一步答辩现场经常要花 15 分钟折腾依赖气势一下子就散了。希望这篇拆解能把一个「听起来普通」的期末大作业做成简历上可以写一笔的完整项目——重点不是选课本身而是你用什么方式保证数据一致、约束合理、代码可维护。把这套思路消化掉换成图书管理、考试报名、实验室预约同类题目也能直接套。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进