简介:面向数据库系统课程设计,这份资料提供了一个基于 Python + PyQt5 + MySQL 的酒店管理系统完整方案。系统覆盖员工管理、客房管理、客户管理等模块,支持个人/团队入住、预约与退房,并配有权限分级、增删改查操作。源码可直接运行,适合需要完成课设、学习 GUI 编程或熟悉 MySQL 前后端联动的学生参考。压缩包内含 61 个文件,除 Python 脚本(18个 .py 及编译缓存 .pyc)外,还有 PyQt5 界面文件(.ui)、数据库脚本(.sql)、配置与项目说明(.xml/.json/.md)以及课程设计报告(PDF 与 E-R 图、功能结构图),总体积 8.28MB,结构清晰便于对照学习。目前已有 209 人学习下载,可作为数据库设计、界面开发与报告撰写的直接素材。
1. 数据库课设“可直接运行”的真相:pyqt5+mysql这套组合,远没有表面那么简单
期末周从网盘里解压出一套“可直接运行”的酒店管理系统,pyqt5的界面、mysql的库、word版课设报告都齐了。按说明装好python、导入sql、执行python main.py,窗口弹出来两秒就闪退,或者卡在“数据库连接失败”——这是很多人对“可直接运行”的第一印象。这个标题想传达的其实是一套完整方案:python写业务逻辑、pyqt5做GUI编程、mysql做数据存储,再配一份能讲清楚表结构和功能的报告。适合正在做数据库课设、想借鉴但不想全盘照抄的人。你需要的不只是点开即用,而是能跑起来、改得动、答辩时讲得明白。真正的门槛藏在环境版本、驱动连接和sql脚本细节里,这篇就把这几道坎逐个拆掉。
2. 拆开标题看选型:python+pyqt5+mysql为什么是课设里的“黄金组合”
数据库课设的可选方案其实很多:C#的WinForm、Java的Swing、网页端的HTML+PHP、或者python+tkinter。但这个标题把票投给了python+pyqt5+mysql,我做了几年课设辅导,这套组合确实是目前学生里最常见也最好出效果的选择。原因不复杂:python上手快,pyqt5能让界面看起来像个正式软件,mysql能承载课程要求的表设计、事务、外键这些知识点。三样东西的安装和调试资料都多,遇到问题搜得到答案,对课设周期来说很重要。
2.1 GUI编程选pyqt5而不是tkinter:控件、布局和信号槽的差别
先解决一个最常被问的问题:python自带的tkinter不也能做GUI吗,为什么要多装一个pyqt5?我的回答是:tkinter做出来的界面像内部测试工具,pyqt5做出来像能交付的产品。差别体现在三个地方:
控件丰富度上,pyqt5自带QTableWidget、QDateEdit、QComboBox这些业务系统里高频使用的组件,尤其是表格控件,显示房间列表和订单记录几乎是课设刚需;tkinter的表格需要自己拼,做出来很吃力。布局管理上,pyqt5的布局系统比tkinter的pack/grid更严谨,窗口拉大缩小时控件不会乱跑。最核心的是信号槽机制,按钮点击、表格选中、窗口关闭这些事件通过signal.connect(slot)绑定,界面逻辑和业务逻辑被拆开了,写起来比tkinter到处绑command清晰得多。
还有一个现实因素:pyqt5有Qt Designer可视化拖拽工具,改界面不用纯手写代码。这对课设阶段“改来改去”的需求非常友好。加上国内教程多,从安装到打包exe都有完整案例,遇到报错能快速搜到解决方案。所以如果你拿到的这套酒店管理系统用的是pyqt5,别急着换成tkinter,界面这关pyqt5已经帮你过了大半。
2.2 酒店业务的数据模型:从入住、退房到报表,表结构该怎么切
一套酒店管理系统的核心是“房态”和“订单”,这两张表设计好了,整个系统的骨架就稳了。我一般建议按这个思路切表:
房间表room、客人表guest、入住订单表checkin、退房历史表checkout、操作员表user。其中room和checkin是核心,字段设计要能回答课设老师最爱问的三个问题:房间状态怎么存、跨表怎么关联、金额为什么不用float。
CREATE TABLE room ( room_id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(10) NOT NULL UNIQUE, room_type VARCHAR(20) NOT NULL, price DECIMAL(10, 2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0空房 1已入住 2脏房' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE checkin ( id INT PRIMARY KEY AUTO_INCREMENT, room_id INT NOT NULL, guest_id INT NOT NULL, checkin_date DATE NOT NULL, days INT NOT NULL, total_amount DECIMAL(10, 2) NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT '1在住 0已退房', CONSTRAINT fk_checkin_room FOREIGN KEY (room_id) REFERENCES room(room_id), CONSTRAINT fk_checkin_guest FOREIGN KEY (guest_id) REFERENCES guest(guest_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这张room表里的status TINYINT NOT NULL DEFAULT 0就是网上常说的“mysql设置默认值为0”的实际场景:新插入的房间记录自动落为空房,不用每次写插入语句都带状态字段。checkin表用room_id和guest_id做外键关联,订单删不掉关联的房间和客人,这是数据库完整性约束的课设考点。
金额字段用DECIMAL(10,2)而不是float,是因为浮点数在计算总价时会有精度误差,退房算钱差几毛钱被老师追问会很难解释。状态字段用TINYINT配注释,比直接存“空房”字符串更规范,也是第二范式的体现——状态是属性,不该重复存储成文本。
2.3 源码目录怎么读:从登录窗口到主界面的调用链
拿到一套源码包,别急着python main.py,先把目录结构看清。常见的酒店管理系统源码会按界面层和数据访问层拆开,大概长这样:
hotel/ main.py # 程序入口,创建QApplication ui/ login_window.py # 登录窗口 main_window.py # 主窗体,包含菜单和业务操作区 db/ db_connect.py # 数据库连接参数 order_dao.py # 订单和房态的数据访问操作 sql/ hotel.sql # 建库建表脚本 课程设计报告.docxmain.py是这个程序的入口,它的调用链一般是:创建QApplication→ 实例化登录窗口 → 登录成功后关闭登录窗口 → 加载主窗口。这个链路之所以重要,是因为课设报告里“系统流程图”那一章画的就是它。
看代码时按这条线走:先看main.py里创建了几个窗口,再看登录窗口的“确定”按钮信号绑到了哪个方法,最后看那个方法里查的是哪张表。一般登录会去user表里比对用户名和密码,密码多半是明文存储,课设阶段可以接受,但报告里最好提一句“生产环境应使用MD5或SHA256加密”,这是加分项。
2.4 为什么报告里必须画ER图和调用链图:课程设计的评分逻辑
数据库课程设计和写代码比赛不一样,老师评分的重心不在功能多少,而在设计是否规范。我见过好几个学生功能做得很全,但报告里没有ER图,或者ER图画得和表结构对不上,最后分很低。原因是课设的全称是“数据库系统课程设计”,数据库设计才是考核重点。
报告里必须有这么几样:概念设计阶段的ER图,要画出room、guest、checkin这几个实体以及它们之间的关系;逻辑设计阶段的表结构定义,每张表的字段、类型、约束、默认值都要写清楚;物理设计阶段的建表SQL。这三层从概念到落地层层递进,是课程设计报告的核心逻辑。
另外系统流程图和界面截图也不能少。流程图对应“调用链”,界面截图对应“功能实现”。截图至少包含登录前、登录后、开房操作、退房操作、报表查询五张。老师翻报告时最先看的是图,图齐了印象分就上去了。
3. 把“可直接运行”变成现实:环境搭建、数据库导入与第一次启动
这一章是整套流程里最容易翻车的地方。很多人拿到源码后卡在第一步:pyqt5装不上,mysql连不上,sql脚本导入报错。其实每一步都有固定套路,按顺序做完就能跑起来。先说结论:不要用最新版python,不要用全局pip环境硬装,不要跳过sql脚本直接点运行。
3.1 python与pyqt5安装:换源、虚拟环境和三个版本坑
pyqt5安装的报错九成出在环境和源的问题上。先把python装好,版本选3.8到3.11之间,别追最新版,有些依赖包对最新python的支持还没跟上。装的时候勾选“Add Python to PATH”,这是新手最容易漏的步骤。
# 1. 验证python是否装好 python --version # 2. 把pip默认源换成国内镜像,解决超时问题 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple # 3. 创建虚拟环境,避免和系统其他工具冲突 python -m venv venv venv\Scripts\activate # windows # source venv/bin/activate # linux/mac # 4. 安装pyqt5 pip install pyqt5 pyqt5-tools这里解释一下为什么必须在虚拟环境里装。很多人电脑上已经装过labelme(图像标注工具),它依赖的是旧版pyqt5。如果你在同一个全局环境里执行pip install pyqt5,pip会为了满足labelme的依赖要求而把pyqt5降级或报冲突,这就是网上常搜到“labelme无法安装pyqt5”这类问题的根源。虚拟环境能把课设项目隔离开,互相不污染。
pyqt5装完后验证一下能否导入:
python -c "from PyQt5.QtWidgets import QApplication; print('pyqt5 ok')"能打印出pyqt5 ok就说明GUI环境没问题。这步验证很关键,能排除掉“pip显示装好了但实际跑不了”的隐性问题。
3.2 mysql安装、建库与sql脚本导入:workbench和命令行两条路
数据库部分分两步:装mysql、导入sql脚本。mysql装8.0社区版就可以,安装时记住root密码,Windows下装完会有一个MySQL80的服务,默认开机自启。如果安装时没设置服务名,也可以在“服务”窗口里手动启动。
导入sql脚本有两条路,命令行和workbench都行。我建议用命令行,因为报错信息更直接:
# 先确认mysql服务已启动 # windows: net start mysql80 # linux: systemctl start mysqld # 登录mysql mysql -u root -p # 在mysql命令行里执行(假设脚本内已包含CREATE DATABASE语句) mysql> source D:/hotel/sql/hotel.sql; # 或者直接通过shell导入(需要脚本里有建库语句) mysql -u root -p < D:/hotel/sql/hotel.sql导入前先把sql脚本用记事本打开看一眼,重点看开头有没有CREATE DATABASE和USE语句。如果只有建表语句而没有建库语句,需要先手动建库再导入:
CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hotel_db;导入完成后验证一下表是否都在:
mysql> USE hotel_db; mysql> SHOW TABLES;能列出room、guest、checkin、user等表名就说明导入成功。如果你用的是mysql workbench,操作路径是:Server → Data Import → Import from Self-Contained File,选中sql文件,Target Schema选hotel_db,然后Start Import。两种方式效果一样,选习惯的来。
3.3 从pymysql到界面:数据库连接与登录窗口的最小验证
环境装好、数据库导好,接下来要让程序和mysql对上话。绝大多数源码里会有一个专门放连接参数的配置文件,一般是db_connect.py或者db_config.py。改它之前先看里面导入了什么驱动,常见的是pymysql。
# db/db_connect.py import pymysql DB_CONFIG = { "host": "localhost", "port": 3306, "user": "root", "password": "123456", "database": "hotel_db", "charset": "utf8mb4", } def get_conn(): return pymysql.connect(**DB_CONFIG)这段代码里的每个参数都有讲究。host填localhost还是127.0.0.1会影响连接方式,localhost在某些系统上会走socket文件,127.0.0.1强制走TCP端口。port默认3306,如果安装mysql时改过端口要同步改这里。password是你安装mysql时设置的root密码,这行是课设里唯一必须改成自己的地方。charset填utf8mb4而不是utf8,因为utf8mb4才能完整支持中文和特殊字符。
这里要单独提醒一个坑:pyqt5自带的QSqlDatabase虽然能连mysql,但pyqt5的pip包默认不带mysql驱动,连的时候会报“Driver not loaded”。绕开这个坑的常见做法就是像上面这样直接用pymysql写数据访问层,界面里调用get_conn()拿连接,完全够用,答辩时SQL还能讲得更清楚。
验证连接是否成功,单独建一个测试文件跑一下:
python -c "from db.db_connect import get_conn; print(get_conn())"能打印出连接对象而不是抛异常,数据库这关就过了。
3.4 第一次启动:登录、进主界面、跑通“开房”流程的3分钟检查
所有前置条件满足后,可以启动程序了:
python main.py启动后程序应该先弹登录窗口,输完账号密码能进主界面。如果卡住或闪退,先看控制台打印的最后一行异常信息,这是排查的第一入口。登录成功后不要急着点所有按钮,按业务主线跑一遍:开房 → 查看房间状态 → 退房 → 查看历史订单。
跑完开房操作后,用SQL验证数据真的写进去了:
SELECT room_no, status FROM room ORDER BY room_no; SELECT id, room_id, guest_id, total_amount FROM checkin ORDER BY id DESC;如果room表里对应房间的status变成了1,checkin表里多了新订单,说明数据库链路是通的。这里体现的正是“mysql设置默认值为0”的用处:新增房间时不用手动写状态,默认就是空房;开房后程序把状态置1,退房后置0,一张表的字段状态流转就是整个系统的核心逻辑。
4. 把课设改成自己的设计:界面、表结构与业务逻辑的自定义方案
直接把源码原封不动交上去,风险很大。同班同学可能共享了同一份资源,老师一查重就能看出来。这一章讲怎么在现有基础上做出自己的设计,不推翻重来,改动量可控。核心思路是:界面换个皮、表加一张、逻辑改一段。
4.1 用Qt Designer改界面:pycharm配置、拖控件与pyuic5转换
改界面最稳妥的方式是用Qt Designer可视化操作,而不是在python代码里手写布局。pyqt5装完后,designer.exe一般藏在虚拟环境的site-packages里,路径类似venv/Lib/site-packages/qt5_applications/Qt/bin/designer.exe。可以把它配置到pycharm的External Tools里,之后双击.ui文件就能打开可视化编辑器。
改界面的常见操作:在主窗口的菜单栏加一个“关于”选项,在开房窗口里加一个“会员折扣”标签,或者把窗口背景色和按钮样式换掉。样式可以直接在Qt Designer里选中控件,在styleSheet属性里填一行代码:
QPushButton { background-color: #2c3e50; color: white; border-radius: 4px; } QTableWidget { gridline-color: #bbb; font-size: 12px; }改完保存,Qt Designer会生成.ui文件,但python代码不能直接运行它,需要转成.py:
pyuic5 -x login_window.ui -o login_window.py这个命令会把界面定义转成python类。注意一个原则:转出来的代码不要手动改,它是自动生成的,改了之后下次重新转就覆盖了。自定义逻辑应该写在另一个文件里,import这个生成的界面类再扩展。
4.2 加一张会员表:扩展表结构的建表SQL与界面联调
如果全班都用同一套表结构,老师看多了会疲劳。加一张 “会员表” 是成本低、效果明显的差异化手段——它能让开房流程从“填客人信息”变成“输入手机号自动带出折扣”。
CREATE TABLE member ( id INT PRIMARY KEY AUTO_INCREMENT, mobile VARCHAR(11) NOT NULL UNIQUE, discount DECIMAL(3, 2) NOT NULL DEFAULT 1.00, balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这张表的几个设计点:mobile加UNIQUE约束,保证一个手机号只能注册一次,这符合业务常识;discount用DECIMAL(3,2),支持0.80、0.90这类折扣;created_at用DEFAULT CURRENT_TIMESTAMP,插入时自动写当前时间,不用手动传参。
表建好后,在开房窗口里加一个“查询会员折扣”按钮,点击后按手机号查会员表:
def query_member_discount(self): mobile = self.mobile_edit.text().strip() sql = "SELECT discount FROM member WHERE mobile = %s" conn = get_conn() with conn.cursor() as cur: cur.execute(sql, (mobile,)) row = cur.fetchone() if row: self.discount_label.setText(f"会员折扣: {row[0]}") else: self.discount_label.setText("非会员,无折扣")这段代码用的是参数化查询,%s是占位符,值通过(mobile,)传进去,而不是拼进SQL字符串。这样做有两个原因:避免SQL注入风险,这是课设答辩的高频追问点;避免中文或特殊字符导致SQL语法错误。
4.3 业务逻辑替换:退房时自动算钱与房态流转
很多参考源码的退房逻辑是“弹窗手动输入总价”,这个设计在答辩时很容易被老师质疑:总价明明可以算,为什么让人手输入?改成自动计算是目前最常见的优化方向。
def checkout_order(self, order_id): conn = get_conn() try: with conn.cursor() as cur: # 查订单的入住天数和房间单价 cur.execute( "SELECT c.days, r.price, c.room_id FROM checkin c " "JOIN room r ON c.room_id = r.room_id " "WHERE c.id = %s AND c.status = 1", (order_id,) ) row = cur.fetchone() if not row: raise Exception("订单不存在或已退房") days, price, room_id = row total = round(float(price) * int(days), 2) # 更新订单状态和金额 cur.execute( "UPDATE checkin SET status = 0, total_amount = %s WHERE id = %s", (total, order_id) ) # 释放房间 cur.execute("UPDATE room SET status = 0 WHERE room_id = %s", (room_id,)) conn.commit() except Exception as e: conn.rollback() raise e finally: conn.close()这段逻辑的关键不在算钱,而在于“订单状态更新”和“房间状态释放”被放在同一个事务里。这两个操作要么都成功,要么都失败,不可能出现“订单退了但房间还是占用”的脏数据。commit()提交事务,异常时rollback()回滚,这是数据库事务ACID特性的实际落地,答辩时把这个点讲清楚,比多做十个按钮都管用。
4.4 课设报告与代码对得上:ER图、测试用例表与截图清单
报告与代码不一致是课设里最常见的失分点。比如报告里写了“支持会员管理”,代码里却没有会员表;ER图画的是三张表,数据库里实际有五张。写报告前先对着代码和数据库过一遍,确保每一处功能和表都有对应出处。
报告结构建议按这个顺序组织:
| 报告章节 | 对应素材 | 常见失分点 |
|---|---|---|
| 需求分析 | 功能列表、用例图 | 功能描述和实际系统不一致 |
| 概念设计 | ER图 | 实体关系缺属性、缺联系 |
| 逻辑设计 | 表结构定义 | 字段类型和约束写错 |
| 物理设计 | 建表SQL | 字符集、存储引擎没写 |
| 系统实现 | 界面截图、核心代码 | 截图和代码对不上 |
| 测试 | 测试用例表 | 没有异常场景测试 |
测试用例表至少写三组:正常开房退房流程、错误密码登录、重复开同一间房。每行记录“输入数据、预期结果、实际结果”。把并发开房这条写进测试用例里,配合上一小节的锁机制,是答辩时能主动引出的亮点。
5. 避坑清单:跑这套GUI课设最常见的5条翻车记录
这一章全是血泪经验。环境问题、连接问题、编码问题,单独看每个都简单,但叠加在一起就能耗掉一整天。按“现象 → 原因 → 解决”的方式写,方便你对着排查。
5.1 pyqt5装不上、labelme冲突、pip超时
现象:执行pip install pyqt5时一直卡在下载阶段,最后报超时;或者提示ERROR: pip's dependency resolver does not currently take into account all dependencies。部分电脑报ERROR: Could not install packages due to an OSError: [Errno 13] Permission denied。
原因:默认pip源在国外,下载速度慢;又或者在全局环境里已经装了labelme这类依赖旧版pyqt5的工具,版本解析直接冲突;Win系统下没权限写全局site-packages。
解决:pip换国内镜像源;创建虚拟环境并激活后再装;不要用sudo pip install或管理员权限硬装。这三个操作缺一不可。换源命令:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple虚拟环境激活后,pip list确认环境是干净的再装pyqt5。如果之前labelme和pyqt5已经互相污染,最简单的办法是删掉旧虚拟环境重新建一个。
5.2 连接mysql报2002/2003/1045:从socket到端口再到密码
现象:Linux下运行程序时控制台报error 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'。Windows下报2003: Can't connect to MySQL server on 'localhost:3306' (10061)。还有一种1045 Access denied for user 'root'@'localhost'。
原因:2002是mysql服务没启动或者socket路径不对,多发生在Linux/macOS环境;2003是windows下端口没监听,服务没起来;1045是密码错误或者用户名不对。
解决:先确认服务状态,再验证端口,最后测密码。Linux下执行:
systemctl status mysqld服务没启动就systemctl start mysqld。Windows下在服务管理器里看“MySQL80”是否在运行,没运行就启动。端口检查:
netstat -ano | findstr 3306能看到LISTENING说明端口正常。如果前两项都没问题但程序还是报2002,大概率是连接串里host写了localhost导致走了socket,改成127.0.0.1强制走TCP:
mysql -u root -p -h 127.0.0.1 -P 3306能登进去说明TCP方式没问题,把代码里的host同步改成127.0.0.1即可。
5.3 中文乱码:建库、连接、界面三层编码不统一
现象:往mysql里插入中文后,用workbench查出来是问号;或者界面上显示正常,但mysql命令行里看是乱码;更隐蔽的一种是Python程序读出来的中文变成??或抛编码异常。
原因:字符集在“建库 → 连接 → 界面显示”三个环节里没统一。mysql 8.0默认字符集是utf8mb4,但sql脚本里建库时可能指定了latin1;或者pymysql连接时没写charset;又或者sql脚本本身是GBK编码保存的,导入时被读错。
解决:三层全部统一成utf8mb4。建库时显式指定:
CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;pymysql连接参数里加charset="utf8mb4"。sql脚本文件用编辑器另存为UTF-8编码再导入,别用记事本默认的ANSI。还有一个坑:mysql workbench导入脚本时,右下角编码要选UTF-8,不是默认的Auto Detect。
这套操作做完,中文问题基本绝迹。如果还有问题,检查表和字段的字符集是不是在建表语句里被单独覆盖了,比如VARCHAR(20) CHARACTER SET latin1这种低级错误。
5.4 打包exe后闪退与杀软误报
现象:pyinstaller --onefile打包后在本地能双击打开,拷到别的电脑上闪退;或者杀毒软件直接把exe删掉并报木马名。
原因:--onefile模式运行时把所有依赖解压到临时目录,首次启动慢且容易被杀软扫描拦截;pyqt5的插件目录没被正确打包时,窗口能弹但很快就崩;部分杀软对python打包的exe存在误报。
解决:改用--onedir模式打包,生成一个文件夹而不是单文件,误报率大幅降低,启动速度也更快:
pyinstaller -w --onedir --clean --noconfirm main.py参数说明:-w去掉命令行黑框,--clean清理上次打包缓存,--noconfirm覆盖dist目录不用二次确认。打包完把整个dist文件夹压缩后发给别人,让对方解压后运行里面的main.exe。
关于杀软误报,没什么好办法,只能接受。打包环境最好用干净的虚拟环境,别在装了很多工具的系统里打包,被误报的概率会小一些。另外如果代码里用了pymysql,有时候需要手动加--hidden-import pymysql,否则打包出来的exe运行时找不到驱动。
5.5 并发开房后订单被覆盖:先查再写不是事务
现象:两个前台同时给不同客人开同一间空房,两笔操作都提示成功,但数据库里只有一条订单,后写入的覆盖了先写入的。
原因:开房流程通常是“先查房间状态是否为空,再插入订单,再更新房间状态”。两个窗口同时执行第一步时,都查到房间是空的,然后各自执行后续写入,后执行的覆盖了先执行的。这属于并发写入冲突,是mysql事务和锁的问题。
解决:把“查状态加更新状态”合成一条原子SQL,让数据库来保证不冲突:
def checkin_room(self, room_id, guest_id, days): conn = get_conn() try: with conn.cursor() as cur: # 原子操作:只有status=0时才能成功更新 cur.execute( "UPDATE room SET status = 1 WHERE room_id = %s AND status = 0", (room_id,) ) if cur.rowcount == 0: raise Exception("房间已被占用") cur.execute( "INSERT INTO checkin (room_id, guest_id, checkin_date, days, status) " "VALUES (%s, %s, CURRENT_DATE, %s, 1)", (room_id, guest_id, days) ) conn.commit() except Exception: conn.rollback() raise finally: conn.close()关键在cur.rowcount == 0:如果房间已经被别的连接改成1,这条UPDATE影响的行数就是0,说明抢房失败,直接抛异常。这个方法比“先SELECT再UPDATE”安全得多,也是在不用显式锁的情况下处理并发的最简方案。把这个逻辑讲给答辩老师听,他会知道你是真明白事务的。
6. 进阶收尾:用连接池接管数据库访问,顺便把答辩自检做到位
运行正常、界面改完、报告写完,整个课设其实已经及格了。最后再做一个提升:把散落在各窗口里的get_conn()收拢成一个连接池。这个改动工作量不大,但能让代码结构明显上一个档次,也是答辩时能主动展示的技术亮点。
6.1 DBHelper单例:让所有窗口共用一套连接池
现在的代码里每点一次按钮就pymysql.connect一次,这种方式叫“短连接”。它的问题在于每个连接都要经历TCP握手和身份验证,窗口一多系统会明显变慢。常见做法是用连接池复用连接,pymysql生态里对应的是dbutils库:
from dbutils.pooled_db import PooledDB import pymysql class DBHelper: _pool = None @classmethod def init_pool(cls, config): cls._pool = PooledDB( creator=pymysql, maxconnections=20, mincached=2, maxcached=10, host=config["host"], port=config["port"], user=config["user"], password=config["password"], database=config["database"], charset=config["charset"], autocommit=False, blocking=True, ) @classmethod def get_conn(cls): return cls._pool.connection()maxconnections=20限制最大连接数,blocking=True表示连接耗尽时排队等待而不是报错,autocommit=False配合手动commit才能用好事务。全程序启动时调用一次DBHelper.init_pool(DB_CONFIG),之后所有窗口都通过DBHelper.get_conn()拿连接,用完close()归还池子。
这个改动的意义不只是性能,它让“连接管理”从每个窗口的重复代码里抽离出来,变成一层独立的数据库访问基础设施。答辩时老师问“你的连接是怎么管理的”,你把这套单例和连接池讲清楚,就已经超出了课设的基本要求。
6.2 答辩现场的自检清单:演示脚本、截图与三个会被追问的点
每次答辩前我都会让学生按这张表自测一遍:
| 检查项 | 做法 | 为什么 |
|---|---|---|
| 预演完整流程 | 登录 → 开房 → 退房 → 查报表,连续跑两遍 | 避免现场连接超时或输入失误 |
| 准备异常测试 | 故意输错密码,演示错误提示 | 展示容错设计,别只跑正确流程 |
| 确认截图与代码一致 | 对照报告里的每张截图重新操作一次 | 防止截图是旧版本的 |
| 准备事务和锁的答案 | 讲清楚5.5节的原子更新逻辑 | 最高频的追问方向 |
| 确认编码与数据正常 | 现场插入一条中文客人姓名 | 演示utf8mb4配置有效 |
最后一个容易被追问的点是“房间状态和订单记录为什么不在一个表里”。答案是:订单对应一次入住行为,房间是持续存在的实体,两者是一对多关系,拆表符合第三范式;如果合在一张表里,房间的单价等属性会在每笔订单里重复存储,产生冗余。这个答案背熟,基本能应对数据库设计类的问题。
我自己跑这套流程时吃过“先SELECT再UPDATE”的亏,后来把并发写入的逻辑改成单条原子SQL,数据库课的答辩就稳了。环境搭通、表结构讲明白、事务锁说清楚,这套课设就算真正落地了。希望帮到你。
本文还有配套的精品资源,点击获取