☰
Python+MySQL酒店管理系统:数据库课程设计高分实现与避坑指南
2026/9/26 18:21:50 网站建设 项目流程

简介:这是一份面向数据库课程设计与期末大作业的Python酒店管理系统完整项目,适合有数据库或Python基础、正在准备课程设计的学生参考。项目以酒店管理业务为场景,涵盖客房信息管理、入住退房、账单报表等典型功能,并将代码、文档与使用教程打包在一起,下载后按说明部署即可运行。压缩包共61个文件,整包约8.3MB,其中以18个Python源码文件、8个UI界面文件、3个SQL数据库脚本和2份PDF系统设计报告为主,另附E-R图、功能结构图、XML配置与README说明,文件类型覆盖源码、界面、数据库、文档多个维度,目录结构清晰,便于按模块查阅。代码中带有详细注释,关键业务逻辑容易理解;SQL脚本可导入MySQL数据库,结合E-R图和设计报告能快速理清表结构、字段含义及数据关联;使用教程则对运行环境、启动步骤和常见问题做了说明,降低了上手门槛。目前已有281人学习下载,整体完成度和规范性较好,适合作为期末大作业或课程设计的高分参考与二次开发基础。

1. 数据库大作业为什么都爱做酒店管理系统:一个晚上能跑通的高分选题

期末还剩两周,数据库课设题目还没定,身边十个同学里至少有三四个会告诉你:做酒店管理系统。这个题目几乎是数据库大作业里的必争之地——业务不复杂,三张表就能讲清楚;又有完整的前台动作,入住、退房、查询、结算全都落在数据库增删改查上;而且网上现成的基于 python 酒店管理系统源代码和文档说明一抓一大把,改一改就能交。这篇笔记按我做课设辅导的经验,把整个高分项目拆开讲:数据库表怎么设计、Python 业务层怎么写、文档和使用教程怎么补完,以及最容易被老师问住的那些坑。适合正在做数据库课程设计、或者想用这个方向练手 python 入门的读者,照着写基本能避开八成翻车现场。

2. 先把数据库立住:三张表的设计、建库 SQL 和 MySQL 的选型理由

2.1 业务边界与三张表:酒店系统为什么不需要十张表

做任何课设之前,先做减法。酒店管理系统听起来很大,真正绕不开的核心操作只有四件事:房间维护、客人登记、入住退房、查询统计。这四件事恰好覆盖数据库的增删改查,又不需要碰支付、会员积分、多门店这些会让表结构爆炸的需求。

对应到数据模型就是三张表。room 表管房间信息和房态,guest 表管客人档案,orders 表管一次入住订单,用 room_id 和 guest_id 把另外两张表关联起来。这个设计天然满足第三范式:订单表里只存外键,不冗余客人的姓名、电话,也不冗余房间的房号和价格,因为这两份信息随时可以从另外两张表查出来。老师很喜欢问一句:"你这订单表为什么不直接存客人名字?"答案就是:一旦客人电话改了,你只改 guest 表一处,订单表不会出现脏数据。这句话值得在文档和答辩里反复强调。

还有一种常见设计是把房型拆成第四张表 room_type,把价格和房型描述抽出去。我的建议是课设阶段不要主动拆,除非你能讲清楚"套房周末加价"这类业务场景。刻意多拆表而不给理由,反而会被追问到哑口无言。房态字段也不要存中文,用 TINYINT 存 0 空闲、1 入住、2 停用,查询条件干净,也方便后端逻辑判断。

2.2 建库建表 SQL:InnoDB、utf8mb4、外键约束一次写对

建库脚本是整个项目的根,后面所有代码都建立在这几行 SQL 上。以下是我一般会用的 init.sql 核心部分:

CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4; USE hotel_db; 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, floor INT NOT NULL, status TINYINT NOT NULL DEFAULT 0, INDEX idx_room_status(status) ) ENGINE=InnoDB; CREATE TABLE guest ( guest_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL UNIQUE, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; CREATE TABLE orders ( order_id INT PRIMARY KEY AUTO_INCREMENT, room_id INT NOT NULL, guest_id INT NOT NULL, checkin_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, checkout_time DATETIME, deposit DECIMAL(10,2) NOT NULL DEFAULT 0, total_amount DECIMAL(10,2), refund DECIMAL(10,2) DEFAULT 0, status TINYINT NOT NULL DEFAULT 0, CONSTRAINT fk_order_room FOREIGN KEY (room_id) REFERENCES room(room_id), CONSTRAINT fk_order_guest FOREIGN KEY (guest_id) REFERENCES guest(guest_id), INDEX idx_order_status(status) ) ENGINE=InnoDB;

几个选型理由值得写进文档。表引擎必须用 InnoDB,因为入住和退房是典型的事务场景,MyISAM 不支持事务,一旦中间步骤失败就会出现"房间占用了但订单没建成"的脏状态。字符集全链路指定 utf8mb4,这是防止中文乱码的第一步。金额字段用 DECIMAL(10,2) 而不是 FLOAT,浮点数的二进制表示会让 288 变成 287.9999。外键约束一定要建,这是数据库课设的评分点,后面删除房间时踩的坑也来自这里,见第 5 章。

表名特意用 orders 而不是 order,因为 ORDER 是 MySQL 的保留字,直接建表会报 1064 语法错误,这是老手也容易犯的毛病。插入几条初始数据跑通系统:

INSERT INTO room(room_no, room_type, price, floor, status) VALUES ('201', '大床房', 288.00, 2, 0), ('202', '双人间', 328.00, 2, 0), ('301', '大床房', 368.00, 3, 0), ('302', '套房', 668.00, 3, 0);

2.3 为什么选 MySQL 而不是 SQLite:连接配置、可视化管理工具和评分视角

直接说结论:课设选 MySQL,别选 SQLite。SQLite 是单文件嵌入式数据库,零配置就能跑,做 python 入门练习很香,但拿到数据库课设里是减分项——老师问"你的事务隔离级别是什么""你的连接池怎么配""用户权限怎么管",SQLite 一个都答不上来。

MySQL 是真实的服务进程,有端口、有用户权限、有独立的连接配置,正好把课设该有的"工程感"做出来。日常开发用 Navicat 连接数据库,可视化地导入 init.sql、看表结构和数据,比在黑框里敲命令直观得多,这也是主流酒店管理系统开发里的常规操作。连接参数集中在 config.py 里,答辩前换机器只改密码一个字段就行:

# config.py DB_CONFIG = { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "123456", "database": "hotel_db", "charset": "utf8mb4" }

这里有个细节:host 建议直接写 127.0.0.1 而不是 localhost。部分环境里 pymysql 解析 localhost 会走 IPv6 的 ::1,MySQL 默认只监听 127.0.0.1,结果就是连接被拒,这个坑第 5 章还会展开。另外如果你装的是 MySQL 8.x,默认密码插件是 caching_sha2_password,跑 pymysql 时会报认证插件错误,解决办法也放在第 5 章。

3. Python 业务层怎么写:pymysql 连接、参数化查询和事务提交

3.1 工程骨架:配置、连接、业务、入口四层各管什么

源代码如果全堆在一个 main.py 里,跑得通但经不起问。我一般把项目拆成这样,文件少但分层清楚:

hotel/ ├── init.sql # 建库建表脚本,答辩前重新初始化用 ├── config.py # 数据库连接参数 ├── db_conn.py # 连接封装 ├── service.py # 入住/退房/查询等业务函数 ├── main.py # 命令行菜单入口 ├── README.md # 使用教程 └── 文档说明.docx # 需求分析 + ER 图 + 数据字典

config.py 只放连接参数,db_conn.py 只负责建立连接。这样做的理由很实际:答辩换到老师电脑上,数据库密码大概率不一样,只改 config.py 一个文件就是后悔药;而连接逻辑和业务逻辑分开,老师问"你的代码分了几层"时你能答得比绝大多数人清楚。

# db_conn.py import pymysql from config import DB_CONFIG def get_conn(): # 每次调用建立独立连接,课设规模下够用 return pymysql.connect(**DB_CONFIG)

注意 pymysql.connect 不是线程安全的懒加载连接,每次业务操作都通过 get_conn() 拿新连接,用完在 finally 里关闭。这个模式简单可靠,等做到第 6 章的连接池再优化,现在是课程作业,稳定第一。

3.2 入住登记核心代码:查房、写客人、建订单、改房态四步一个事务

入住登记是系统里最值得写进文档的功能,因为它一次演示了参数化查询、事务、外键和状态机。代码逻辑分四步:查空闲房间、写客人档案、创建订单、把房间状态改成已入住。

# service.py import pymysql from datetime import datetime from db_conn import get_conn def check_in(room_no, name, id_card, phone, deposit): conn = get_conn() try: with conn.cursor() as cur: # 1. 查房间,FOR UPDATE 在事务内锁住这行,防止两人同时抢房 cur.execute( "SELECT room_id, price FROM room " "WHERE room_no=%s AND status=0 FOR UPDATE", (room_no,) ) room = cur.fetchone() if room is None: raise RuntimeError(f"房间 {room_no} 不存在或已被占用") room_id, price = room # 2. 写客人;身份证号重复时复用旧档案并返回已有 guest_id cur.execute( "INSERT INTO guest(name, id_card, phone) VALUES(%s, %s, %s) " "ON DUPLICATE KEY UPDATE " "guest_id=LAST_INSERT_ID(guest_id), name=VALUES(name), phone=VALUES(phone)", (name, id_card, phone) ) guest_id = cur.lastrowid # 3. 创建未结算订单,status=0 cur.execute( "INSERT INTO orders(room_id, guest_id, deposit, status) " "VALUES(%s, %s, %s, 0)", (room_id, guest_id, deposit) ) # 4. 占用房间 cur.execute("UPDATE room SET status=1 WHERE room_id=%s", (room_id,)) conn.commit() return True except Exception as e: conn.rollback() raise e finally: conn.close()

四个参数你必须懂。第一个,所有 SQL 都用 %s 占位符而不是字符串拼接,pymysql 会做参数转义,这是防 SQL 注入的唯一正确写法,老师问"你怎么防注入"就把这段代码指给他看。第二个,ON DUPLICATE KEY UPDATE 配合 LAST_INSERT_ID,是"老客户复用档案"的标准写法,注意 guest 表的 id_card 字段建了唯一索引,这条语句才生效。第三个,SELECT ... FOR UPDATE 把房间行锁住,事务提交前其他连接改不了这行,课设答"并发抢同一间房"的问题就靠它。第四个,四步操作共用一个事务,任何一步抛异常都 rollback,不会留下半截数据。

3.3 退房结算:按天计费、最少一晚和押金退还怎么算

退房是入住的反向操作,但多了一个金额计算,这里最容易在边界条件上翻车。结算逻辑是:查订单、算住几天、乘房价、算押金退还、改订单状态、释放房间。

def check_out(order_id): conn = get_conn() try: with conn.cursor() as cur: # 查出未结算订单,连带房间价格 cur.execute( "SELECT o.room_id, o.checkin_time, o.deposit, r.price " "FROM orders o JOIN room r ON o.room_id=r.room_id " "WHERE o.order_id=%s AND o.status=0", (order_id,) ) order = cur.fetchone() if order is None: raise RuntimeError("订单不存在或已经结算过") room_id, checkin_time, deposit, price = order # 天数:当天入住当天退按 1 晚算,跨天按实际天数 days = (datetime.now() - checkin_time).days if days < 1: days = 1 total = round(days * float(price), 2) refund = round(float(deposit) - total, 2) cur.execute( "UPDATE orders SET checkout_time=NOW(), " "total_amount=%s, refund=%s, status=1 WHERE order_id=%s", (total, refund, order_id) ) cur.execute("UPDATE room SET status=0 WHERE room_id=%s", (room_id,)) conn.commit() return total, refund except Exception as e: conn.rollback() raise e finally: conn.close()

这里有两个细节建议写进文档。一是天数计算用 (now - checkin_time).days 取天数差,但当天入住当天退的情况 days 等于 0,酒店行业惯例最少按一晚算,所以加一个 if days < 1 的兜底。二是 refund 可能是负数,说明押金不够房费,此时业务上应该补收,打印提示时别写成"退还押金"。

最后给 main.py 一个最小命令行入口,让整个系统能跑起来:

# main.py from service import check_in, check_out def menu(): while True: print("1 入住登记 2 退房结算 3 查房态 0 退出") cmd = input("请选择操作:") if cmd == "1": check_in(input("房间号:"), input("姓名:"), input("身份证:"), input("电话:"), float(input("押金:"))) elif cmd == "2": total, refund = check_out(int(input("订单号:"))) print(f"房费 {total} 元,押金结算 {refund} 元") elif cmd == "0": break

这段代码故意很简洁,界面不是课设重点,所有房间的增删改查函数照这个模式补全就行。能把入住、退房跑通,项目的核心完成度已经 80%。

4. 文档、使用教程和演示设计:高分项目的一半功夫在数据库之外

4.1 文档三件套:需求分析、ER 图和数据字典的写法

源代码只占高分项目的一半,另一半是文档说明和使用教程。老师第一眼看的就是文档,文档决定了项目"像不像一个认真做的数据库设计"。三件套分别是需求分析、ER 图、数据字典。

需求分析写两页就够:背景、角色、功能清单。功能清单要分"必做"和"扩展",必做四件事对应第 2 章的四项操作,扩展写"支持会员折扣""支持换房"这类开放性方向,让老师看到你思考过而不是只会抄。ER 图用 draw.io 或亿图画,导出 PNG 贴进文档,实体就是三张表,关系是 room 1 对 N orders、guest 1 对 N orders。不要画复杂的分支,三张表的关系一眼讲得清才是课设的合理复杂度。

数据字典是老师最喜欢翻的一页,每张表一个字段表,写明类型、约束和业务含义。room 表示例:

字段类型约束说明
room_idINTPK, AUTO_INCREMENT房间主键
room_noVARCHAR(10)UNIQUE, NOT NULL房号,物理标识
room_typeVARCHAR(20)NOT NULL房型:大床房/双人间/套房
priceDECIMAL(10,2)NOT NULL门市价,按天计
floorINTNOT NULL所在楼层
statusTINYINTDEFAULT 00 空闲 1 入住 2 停用

数据字典的写法有个技巧:说明列不要复制字段名的英文含义,要写业务语义。比如 status 写"0 空闲 1 入住 2 停用",而不是"状态字段"。这页做完,文档的正文部分基本合格。

4.2 使用教程怎么写:从 python 安装到 vscode 环境配置,一步步可复现

README 使用教程的目标是"换一台干净的电脑,照着做十分钟能跑起来"。很多从免费 python 源码下载的现成项目,教程只有一句"python main.py",依赖和数据库初始化全跳过了,这种教程等于没有。规范的 README 至少包含环境准备、依赖安装、数据库导入、启动四步:

# 1. 确认 python 版本,要求 3.8 以上 python --version # 2. 创建并激活虚拟环境,vscode 里再选这个解释器 python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate.bat # 3. 安装唯一的外部依赖 pip install pymysql # 4. 导入数据库,init.sql 在项目根目录 mysql -u root -p < init.sql # 5. 启动程序 python main.py

每一步都值得写一句解释。虚拟环境这一步最容易忽略,但正是它避免了"装了一堆全局包互相打架"的黑匣子问题。vscode 里做完这步后,按 Ctrl+Shift+P 执行 Python: Select Interpreter,选中 .venv 对应的解释器,python 环境配置才算闭环。数据库导入除了命令行,也可以用 Navicat 连接数据库后直接运行 init.sql 文件,效果一样,两种方式在教程里写一种主路径即可。最后在教程开头加一句:运行前先改 config.py 里的密码为本机 MySQL 密码,能挡住一半新手提问。

4.3 演示脚本:两分钟讲清楚,老师问不倒

答辩演示要提前排演,顺序比内容重要。我的演示顺序固定四步:先打开 Navicat 展示三张表结构,再查一遍空闲房源,然后现场做一次入住、退房全流程,最后跑一条统计 SQL 收尾。整个流程控制在两分钟内,重点不是功能多,而是让老师看清"你的操作对应数据库里哪条数据变了"。

入住的演示配合口头讲解:"现在 orders 表多了一行,room 表 201 的状态从 0 变 1。"退房同理:"订单状态变 1,房态归位,total_amount 是自动算出来的。"老师后续问"如果两个人同时订同一间房怎么办",就答第 3.2 节的 FOR UPDATE 加事务;问"房价策略变了怎么办",就答价格在 room 表,扩展成价格策略表不影响现有逻辑。这两个答案准备好,项目的高分基本稳了。

再说一个实战建议:答辩前一定从 init.sql 重新初始化一次数据库,保证演示环境是干净状态,前面试过退房、改过的脏数据全部清掉。这就是整套方案的后悔药,比任何调试技巧都管用。

5. 避坑排查:连接失败、中文乱码、保留字和外键的高频翻车现场

这一章是血泪经验汇总。以下五个坑是我见学生踩得最多的,按现象、原因、解决三段写,每条都值得提前预防。

5.1 Navicat 连不上 MySQL:先查服务是否启动,再查连接方式

现象:Navicat 或 pymysql 报 "Can't connect to MySQL server" 或 10061 错误,程序一启动就死在这一步。

原因:八成是 MySQL 服务没启动。Windows 下安装 MySQL 后服务默认可能是"手动"状态,重启电脑就没起来;还有两成是 pymysql 的 host 写了 localhost,被解析成 IPv6 的 ::1,而 MySQL 只监听了 127.0.0.1。

解决:先确认服务,Windows 在服务管理器里找 MySQL 并启动,或执行net start mysql80(版本不同服务名可能不同);同时把 config.py 和 Navicat 的 host 统一改成 127.0.0.1。排错时顺手执行ping 127.0.0.1和netstat -ano | findstr 3306,能确认端口是否在监听。

5.2 控制台和数据库里全是问号:字符集要全链路统一 utf8mb4

现象:INSERT 报 "Incorrect string value: '\xE6...'",或者数据存进去中文正常,读出来全是问号。

原因:字符集链路断了一环。常见三种情况:建库时没指定字符集走了默认 latin1;pymysql 连接参数没写 charset;Windows 终端用 GBK 显示,数据库里其实是对的,看起来像乱码。

解决:建库语句写DEFAULT CHARACTER SET utf8mb4,连接参数写charset="utf8mb4",这是两条铁律。终端乱码用chcp 65001切到 UTF-8 后再跑程序,或者去 Navicat 里看数据确认是否真的乱码。排查时执行SHOW CREATE TABLE room;查看表级字符集,别只在代码层面改。

5.3 SQL 语法报错 1064:order 是 MySQL 保留字,别拿它当表名

现象:执行CREATE TABLE order (...)或INSERT INTO order ...直接报 1064 语法错误,反复检查 SQL 都看不出问题。

原因:ORDER 是 MySQL 的保留字,用于 ORDER BY 排序,直接当表名和标识符用会触发语法解析错误。这套路同样适用于 user、group、system 等保留字。

解决:表名统一用复数 orders 绕开保留字,这是最省事的方式;如果非要叫 order,SQL 里必须用反引号包起来,写成`order`。我的建议是直接改名,因为文档、代码、口头描述里到处都要写表名,带反引号反而容易抄错。

5.4 删房间被外键拦住:先处理未结算订单,或者改用状态位软删除

现象:想清理测试数据时执行DELETE FROM room WHERE room_id=1,报错 "foreign key constraint fails"。

原因:orders 表里还有订单引用这个房间的 room_id,外键约束不允许删除被引用的父表记录。这其实说明外键设计生效了,是好事,但演示时处理不当会冷场。

解决:先查有没有未结算的入住订单,处理完再删:

SELECT room_no FROM room r WHERE r.room_id IN (SELECT room_id FROM orders WHERE status = 0);

更常见的工程做法是"软删除":不物理删除房间,而是把 status 改成 2 表示停用,查询空闲房时带上 status=0 条件自然就过滤掉了。这样既保留历史订单的关联完整性,又实现了删除语义,答辩时这段回答是加分项。

5.5 pymysql 报 Authentication plugin:MySQL 8 默认密码插件与旧驱动不兼容

现象:pymysql 连接时报 "Authentication plugin 'caching_sha2_password' cannot be loaded",或者提示缺少 cryptography 包。

原因:MySQL 8.0 把默认密码插件改成 caching_sha2_password,较老版本的 PyMySQL 不认识这个插件,需要额外的 cryptography 库支持加密通信。

解决:两条路任选。优先执行pip install cryptography,装完一般就通了,改动最小;如果不想装,也可以把账号认证方式改回 mysql_native_password:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'; FLUSH PRIVILEGES;

提醒一点:如果用了改插件这条路,换到别的电脑演示前要记得目标机器的 MySQL 也做同样处理,否则同样的代码换个环境又崩。所以我的默认选择永远是补装 cryptography。

6. 再往前走一步:tkinter 界面、月度报表和连接池,够答辩加分

6.1 三天套一个 tkinter 界面:不引第三方库就能交差

命令行版跑通之后,很多老师会问一句"有界面吗"。答案其实不难:Python 自带的 tkinter 就够了,不装任何第三方库,三天能套完。把 service.py 的函数原封不动接进按钮事件就行:

import tkinter as tk from tkinter import messagebox from service import check_in app = tk.Tk() app.title("酒店管理系统") def submit(): try: check_in(no.get(), name.get(), idcard.get(), phone.get(), float(deposit.get())) messagebox.showinfo("提示", "入住成功") except Exception as e: messagebox.showerror("错误", str(e)) tk.Label(app, text="房间号").pack() no = tk.Entry(app); no.pack() # 姓名、身份证、电话、押金四个输入框同理,省略重复代码 tk.Button(app, text="入住登记", command=submit).pack() app.mainloop()

界面代码的价值不是好看,而是证明你的业务函数和界面是解耦的。老师问"换界面会影响业务吗",你答"不影响,service.py 一套逻辑两头用",这句话比界面本身更能拿分。

6.2 报表查询和连接池:把"数据库含量"再抬一档

总停留在增删改查会被认为数据库含量不足,加一张月度报表能明显拉高印象分。统计退房订单的月度营收,一条 SQL 就够:

SELECT DATE_FORMAT(checkout_time, '%Y-%m') AS 月份, COUNT(*) AS 订单数, SUM(total_amount) AS 营收 FROM orders WHERE status = 1 GROUP BY DATE_FORMAT(checkout_time, '%Y-%m') ORDER BY 月份;

这条 SQL 展示了聚合、分组、日期函数三个知识点,答辩时提一句"用 GROUP BY 按月份聚合,SUM 算营收",数据库功底立刻和其他同学拉开差距。再往上就是连接池,课设规模其实用不上,但如果老师问了"高并发怎么办",能答出方案就说明你见过工程实践:

from dbutils.pooled_db import PooledDB import pymysql pool = PooledDB(creator=pymysql, maxconnections=10, mincached=2, host="127.0.0.1", port=3306, user="root", password="123456", database="hotel_db", charset="utf8mb4") def get_conn(): return pool.connection()

注意安装包名是 DBUtils,导入路径是 dbutils.pooled_db,这两个名字不一致,我第一次用的时候也踩过。连接池的思路是复用连接而不是每次新建,maxconnections 控制上限,mincached 保持常驻连接数,正好把第 3 章"每次新建连接"的做法做了一个工程化升级。

说到智慧酒店管理系统这类更完整的方向,无非是在这三张表上继续长:会员表、价格策略表、房态日志表、保洁工单表。但课设的核心始终是先把三张表的关系和事务讲透。我自己的习惯是,答辩前永远做两件事:从 init.sql 重新初始化数据库,然后按演示脚本完整走一遍入住退房。当初我用 FLOAT 存金额被老师当场指出精度问题,从那以后金额一律 DECIMAL,这个教训比任何文档都深刻。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询