简介:面向数据库课程设计的医院病房管理系统项目,涵盖ER建模、关系表设计、Java前后端开发及住院业务流程实现,并涉及关系数据库理论与软件工程方法,可作为高校学生完成课设或毕业设计的参考样板。压缩包共146个文件,以Java源码(37个.java)、编译产物(59个.class)、数据库SQL脚本及工程配置文件为主,并含界面截图与说明文档,整体仅5.04MB,便于下载与本地部署。已有165人学习下载,适合正在规划数据库课设或需要完整项目骨架的读者参考。资源提供了从病人入院登记、病房床位分配、医生排班到费用结算的完整业务模块,通过源码可学习主键外键设计、事务处理与权限控制等关键实践;附带的SQL脚本和项目文档能帮助快速导入数据并理解整体结构,节省从零搭建的时间。
1. 医院病房管理系统:数据库课设资源,先看它解决了什么
第一次看到「医院病房管理系统——数据库课设.zip」这个文件名时,我以为是又一个只能跑通增删改查的练习小项目。真正拆开才发现,它把患者、医生、病房、床位、医嘱、护理记录和费用全部串进了同一套关系模型里,用视图、触发器、存储过程把一张张看似孤立的表连成了完整业务流。A同学答辩前一周拿到这套资源,按顺序执行建库脚本、导入示例数据、再照着报告骨架把设计思路讲清楚,最后顺利过了。这套资源适合正在做数据库课程设计的学生、想快速复现一套完整业务库的开发新人,也适合手里有差不多的课设需求、但不知道表和表之间到底该怎么串的人。它能帮你省掉从零画ER图、纠结字段类型、写不出带事务的存储过程这些最耗时的环节。
2. 表结构设计:从ER模型到八张业务表的落地细节
课设答辩时老师很少问“你用了几个表”,问得最多的是“为什么这张表要这么设计”。所以拿到资源后别急着执行SQL,先把它拆成模块看。这套医院病房管理系统在数据层面可以划分为基础档案、诊疗业务、费用统计三大块,每一块对应两到三张表。
2.1 模块边界与核心表职责
先从资源附带的设计文档里把表结构清单抄出来,对照职责看:
| 模块 | 表名 | 核心职责 | 典型字段 |
|---|---|---|---|
| 基础档案 | doctor | 医生信息 | 科室、职称、工号 |
| 基础档案 | nurse | 护士信息 | 所属病区、排班 |
| 病区管理 | ward | 病房信息 | 病房类型、总床位、可用床位 |
| 病区管理 | bed | 病床信息 | 所属病房、床位号、状态 |
| 诊疗业务 | patient | 患者主档 | 姓名、性别、入院时间、主治医生 |
| 诊疗业务 | medical_order | 医嘱记录 | 医嘱内容、开立时间、执行状态 |
| 护理记录 | nursing_record | 护理操作记录 | 体温、血压、护理备注 |
| 费用统计 | charge_record | 费用明细 | 费用项目、金额、结算状态 |
这个划分是课设里比较标准的答案:医生、护士分开建表,而不是塞进同一张“员工表”,是因为两类人员的属性差异大,后续扩展排班和护理排班都方便。患者表单独建,不把个人信息重复写进医嘱表,符合第三范式。
2.2 建表脚本:字段类型、主键与字符集怎么选
直接把资源里的核心建表语句整理出来,我一般会改写成下面这样再上机执行:
CREATE DATABASE IF NOT EXISTS hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hospital_db; CREATE TABLE ward ( ward_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '病房ID', ward_no VARCHAR(20) NOT NULL UNIQUE COMMENT '病房编号', ward_type VARCHAR(20) NOT NULL DEFAULT '普通病房' COMMENT '病房类型', total_beds INT NOT NULL DEFAULT 4 COMMENT '总床位数', available_beds INT NOT NULL DEFAULT 4 COMMENT '可用床位数' ) ENGINE=InnoDB COMMENT='病房表'; CREATE TABLE bed ( bed_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '病床ID', ward_id INT NOT NULL COMMENT '所属病房ID', bed_no VARCHAR(10) NOT NULL COMMENT '床位号', status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT 'AVAILABLE/OCCUPIED', patient_id INT DEFAULT NULL COMMENT '当前占用患者ID,空则为空床', CONSTRAINT uk_ward_bed UNIQUE (ward_id, bed_no), CONSTRAINT fk_bed_ward FOREIGN KEY (ward_id) REFERENCES ward(ward_id) ) ENGINE=InnoDB COMMENT='病床表'; CREATE TABLE patient ( patient_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '患者ID', name VARCHAR(50) NOT NULL COMMENT '患者姓名', gender VARCHAR(4) NOT NULL COMMENT '性别:男/女', age INT NOT NULL COMMENT '年龄', doctor_id INT NOT NULL COMMENT '主治医生', admit_date DATE NOT NULL COMMENT '入院日期', discharge_date DATE DEFAULT NULL COMMENT '出院日期', bed_id INT DEFAULT NULL COMMENT '当前床位ID', CONSTRAINT fk_patient_doctor FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id), CONSTRAINT fk_patient_bed FOREIGN KEY (bed_id) REFERENCES bed(bed_id) ) ENGINE=InnoDB COMMENT='患者主档表';这段脚本最值得抄的是三个取舍。第一,字符集统一用utf8mb4,不是utf8,因为数据库课程设计里经常有人把“患者姓名”做成表情符号测试,utf8存不了四字节字符,插入直接报错。第二,gender用VARCHAR(4)而不是 MySQL 的ENUM('M','F'),虽然 ENUM 省空间,但课设答辩时老师常问“如果哪天需要存第三类性别怎么办”,改 ENUM 的代价比改字符串大得多。第三,bed表加了patient_id可空列,用来记录当前占用关系。这是一种冗余设计,但能让病床状态查询少一次 JOIN,答辩时可以主动讲出“这是用空间换查询性能”。
需要注意建表顺序。patient引用了doctor和bed,bed引用了ward,所以必须先建ward、doctor,再建bed,最后建patient。如果你直接整体导入还报外键错误,通常就是脚本里的建表顺序没排好。
2.3 外键约束与级联删除:别给病床留反锁
外键是课设的高频考点,也是最容易翻车的地方。资源里默认用的是RESTRICT,也就是子表还有引用时,父表禁止删除。
-- 删除一个还被病床引用的病房,会报外键错误 DELETE FROM ward WHERE ward_id = 1; -- 先删除该病房下所有病床,再删病房才能成功 DELETE FROM bed WHERE ward_id = 1; DELETE FROM ward WHERE ward_id = 1;这里有个经验:病房和病床不要设置ON DELETE CASCADE。看起来级联删除很省事,但真实场景里,病床可能关联过大量历史医嘱和护理记录,一旦级联删除会把整条业务链上的数据全部抹掉。A同学第一次把课设里的外键全部改成 CASCADE,演示“删除病房”功能时,患者基本档案连带没了,当场被老师指出不符合医疗数据保留规范。正确做法是在 app 层先做“停用”操作,给病房加一个is_active字段,把状态置为0而不是物理删除。
外键还有一个隐性要求:两张表之间的关联字段类型必须完全一致。比如ward.ward_id是INT,那bed.ward_id就不能写成BIGINT或INT UNSIGNED,否则 MySQL 会在建表时报“无法创建外键约束”,这个报错信息很玄学,不仔细看类型根本发现不了。
3. 环境与初始化:让建表脚本一次跑通的三个前置条件
很多同学拿到课设包后第一句话是“脚本报错了”。多数时候不是脚本问题,是环境没对齐。这套资源的初始化脚本按 MySQL 语法写的,下面的流程就是我把脚本从报错调到跑通的全过程。
3.1 版本与连接参数
先确认本地数据库版本。用以下命令查看:
mysql -u root -p SELECT VERSION();资源里的脚本兼容 MySQL 5.7 和 8.x,但有个关键差异要提前处理。MySQL 8.x 默认认证插件是caching_sha2_password,如果你用的是较老的图形客户端或驱动,连接时经常报“Authentication plugin cannot be loaded”。常见做法是给使用的账号改回旧的认证方式:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;这条命令只影响指定的账号,不会破坏数据库本身。跑完再连接就正常了。
连接参数同样要统一字符集。JDBC 连接串里至少带上这几个参数:characterEncoding=utf8、useSSL=false、serverTimezone=Asia/Shanghai。少了characterEncoding,中文写进库后再查出来就是问号,后面所有作业等于白做。
3.2 初始化脚本的执行路径
这套资源里的脚本按编号排列,标准执行顺序是建库、建表、插基础数据、插模拟业务数据。我习惯用命令行按顺序执行,错误信息看得最清楚:
# 创建数据库并指定字符集 mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 导入表结构 mysql -u root -p hospital_db < scripts/01_schema.sql # 导入基础字典数据 mysql -u root -p hospital_db < scripts/02_base_data.sql # 导入模拟业务数据 mysql -u root -p hospital_db < scripts/03_sample_data.sql这里有一个必须遵守的顺序原则:先导表结构,再导数据。如果先执行03_sample_data.sql,里面插入的patient记录引用了不存在的表,直接报“Table not found”。另一个容易忽略的点是,如果中间某一条插入语句因为外键问题失败,MySQL 默认不会回滚整个文件,它会把报错语句跳过继续执行后面的语句。所以导入完成后不能只看“有没有报错”,还要抽查数据量。
我习惯在三个脚本全部执行后再跑一条验证语句,确认关键表有数据:
SELECT (SELECT COUNT(*) FROM ward) AS ward_cnt, (SELECT COUNT(*) FROM bed) AS bed_cnt, (SELECT COUNT(*) FROM patient) AS patient_cnt, (SELECT COUNT(*) FROM medical_order) AS order_cnt;只要四行数据都大于 0,说明核心链路已经通了。如果medical_order是 0,通常是03_sample_data.sql里这段业务数据没有命中正确的doctor_id,外键校验把它拦下来了。
3.3 用示例数据验证表关系
建好表、导完数据,接下来的关键动作是验证“病床与患者”的关联是否正确。我自己验证时会手动模拟一次入院,而不是直接相信示例数据:
-- 找一个状态为 AVAILABLE 的病床 SELECT bed_id, ward_id, bed_no FROM bed WHERE status = 'AVAILABLE' LIMIT 1; -- 手动插入一名患者 INSERT INTO patient (name, gender, age, doctor_id, admit_date, bed_id) VALUES ('测试患者A', '男', 58, 1, CURDATE(), 12); -- 同步占用病床 UPDATE bed SET status = 'OCCUPIED', patient_id = LAST_INSERT_ID() WHERE bed_id = 12;注意LAST_INSERT_ID()只在同一个连接里有效。如果你先用图形工具插入了患者,再开一个命令行窗口去更新病床,拿到的值是 0,床位关联就错了。正确做法是 INSERT 和 UPDATE 在同一个会话里连续执行,或者先查出真实patient_id再手动填进去。
顺手提一句UPDATE语句的坑:如果不加WHERE bed_id = 12,会把整张病床表全部改成占用状态。课设演示时这个错误特别容易发生,因为身上压力一大,手一抖就少写了条件。从那以后我每次写 UPDATE 都先写 WHERE,再回去补 SET,养成习惯后基本没再翻过车。
4. 核心功能SQL:视图、触发器与存储过程这样抄作业
课设能不能拿高分,主要看这一层有没有东西。光会建表和增删改查只算及格,视图、触发器、存储过程和权限控制才是答辩时的亮点。这套资源在这块给了比较完整的实现,我的建议是别整段复制,改完参数再上机。
4.1 视图:病房占用情况与患者费用汇总
视图是给“反复使用的复杂查询”准备的。比如病房占用情况,它需要关联病房表、病床表、患者表,每次手写 JOIN 很容易漏,而且容易写出笛卡尔积。资源里用视图把这层查询封装成一张“虚拟表”。
CREATE OR REPLACE VIEW v_ward_occupancy AS SELECT w.ward_id, w.ward_no, w.ward_type, w.total_beds, w.available_beds, COUNT(b.bed_id) AS current_beds, SUM(CASE WHEN b.status = 'OCCUPIED' THEN 1 ELSE 0 END) AS occupied_beds FROM ward w LEFT JOIN bed b ON w.ward_id = b.ward_id GROUP BY w.ward_id, w.ward_no, w.ward_type, w.total_beds, w.available_beds;查询时直接SELECT * FROM v_ward_occupancy WHERE ward_no = '301'就能拿到一个病房的动态占用数。
这个视图有三个要点。第一,用LEFT JOIN而不是INNER JOIN,否则一间空病房(没有病床)会被过滤掉,统计结果就少了数据。第二,GROUP BY必须包含w.ward_id, w.ward_no等所有非聚合列,MySQL 默认允许字段少写,但查询结果是不确定的。第三,CASE WHEN配合SUM是数“满足条件的行数”的标准做法,比COUNT(IF(...))更好读。
费用汇总视图同理,它把费用明细表和结算状态关联起来:
CREATE OR REPLACE VIEW v_patient_bill AS SELECT p.patient_id, p.name, COUNT(c.charge_id) AS charge_count, SUM(CASE WHEN c.status = 'UNPAID' THEN c.amount ELSE 0 END) AS unpaid_amount, SUM(c.amount) AS total_amount FROM patient p LEFT JOIN charge_record c ON p.patient_id = c.patient_id GROUP BY p.patient_id, p.name;这个视图直接回答“某个患者现在还欠多少钱”。UNPAID的统计放在 CASE 里而不是WHERE条件中,是为了保留已缴费记录,方便演示总费用和欠费金额两个指标。
4.2 触发器:病房可住床位自动扣减
触发器属于“让数据库主动干活”的设计。病房表里有total_beds和available_beds两个字段,如果每次状态变更都靠应用代码手动更新,难免有人忘记。资源里用触发器自动维护这两个字段。
DELIMITER $$ CREATE TRIGGER trg_ward_beds_after_bed_update AFTER UPDATE ON bed FOR EACH ROW BEGIN IF NEW.status = 'OCCUPIED' AND OLD.status = 'AVAILABLE' THEN UPDATE ward SET available_beds = available_beds - 1 WHERE ward_id = NEW.ward_id; END IF; END$$ DELIMITER ;触发器逻辑是:当bed表某一行从AVAILABLE改成OCCUPIED,就把所属病房的available_beds减一;反过来从占用改成空床,就加一。
写这个触发器最容易踩的坑是递归更新。如果你在AFTER UPDATE触发器里再次 UPDATEbed表,会再次触发同一个触发器,形成无限嵌套,直接报“Can't update table in stored function/trigger because it is already used by a statement”。正确做法是只更新ward表,因为ward表的变化不会再触发bed表的触发器。另外注意UPDATE ward语句必须带WHERE ward_id = NEW.ward_id,这里的NEW指当前被修改的那行病床数据,不是整个触发器作用域。
如果你买的资源里同时存在“更新病床状态触发器”和“入院存储过程”,一定要测试它们的执行顺序。存储过程执行时第一步UPDATE bed SET status='OCCUPIED'就会激发这个触发器,触发器的 UPDATE 和存储过程后续的 COMMIT 处在同一事务里,任何一个环节出错,整笔入院操作都会回滚。
4.3 存储过程:一次入院的完整事务
存储过程是课设里最能体现水平的部分。它能把“查床位、插患者、占床位”三步操作打包成一个完整事务,中间任何一步失败都回滚。资源里的sp_admit_patient实现得很典型:
DELIMITER $$ CREATE PROCEDURE sp_admit_patient( IN p_name VARCHAR(50), IN p_gender VARCHAR(4), IN p_age INT, IN p_doctor_id INT, IN p_bed_id INT, OUT p_patient_id INT ) BEGIN DECLARE v_bed_status VARCHAR(20); -- 出现任何异常直接回滚 DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; START TRANSACTION; -- 锁定病床行,防止并发下同一张床被分配两次 SELECT status INTO v_bed_status FROM bed WHERE bed_id = p_bed_id FOR UPDATE; IF v_bed_status <> 'AVAILABLE' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '床位不可用'; END IF; INSERT INTO patient (name, gender, age, doctor_id, admit_date, bed_id) VALUES (p_name, p_gender, p_age, p_doctor_id, CURDATE(), p_bed_id); SET p_patient_id = LAST_INSERT_ID(); UPDATE bed SET status = 'OCCUPIED', patient_id = p_patient_id WHERE bed_id = p_bed_id; COMMIT; END$$ DELIMITER ;调用示例:
CALL sp_admit_patient('测试患者B', '女', 29, 1, 13, @new_patient_id); SELECT @new_patient_id;这段存储过程的几个参数需要解释:IN是输入参数,OUT是输出参数,输出的p_patient_id用来让应用层知道新患者的主键;SIGNAL SQLSTATE '45000'是主动抛出业务异常,45000是 MySQL 专门留给用户自定义异常的代码;“EXIT HANDLER”用的是SQLEXCEPTION,表示任何 SQL 语句报错都会进入回滚逻辑。
最关键的语句是SELECT ... FOR UPDATE。如果没有它,两个并发请求可能同时读取到AVAILABLE,然后同时插入两个患者到同一张病床。加上行锁后,第二个请求会等待第一个事务提交,这就是数据库课设里“事务隔离”的现场演示材料,答辩时主动讲出来非常加分。
4.4 权限脚本:按角色最小授权
权限控制是很多课设不做的内容,但这套资源给了比较完整的模型,它把数据库用户分成管理员、医生、护士三个角色。医生只能读患者档案和写医嘱,护士只能写护理记录,管理员才有全部权限。
-- 创建三个业务账号 CREATE USER IF NOT EXISTS 'admin_user'@'localhost' IDENTIFIED BY 'Admin@123'; CREATE USER IF NOT EXISTS 'doctor_user'@'localhost' IDENTIFIED BY 'Doctor@123'; CREATE USER IF NOT EXISTS 'nurse_user'@'localhost' IDENTIFIED BY 'Nurse@123'; -- 管理员:所有库权限 GRANT ALL PRIVILEGES ON hospital_db.* TO 'admin_user'@'localhost'; -- 医生:患者与医嘱的读写 GRANT SELECT, INSERT, UPDATE ON hospital_db.patient TO 'doctor_user'@'localhost'; GRANT SELECT, INSERT, UPDATE ON hospital_db.medical_order TO 'doctor_user'@'localhost'; GRANT SELECT ON hospital_db.v_ward_occupancy TO 'doctor_user'@'localhost'; -- 护士:只读写护理记录,费用只读 GRANT SELECT, INSERT, UPDATE ON hospital_db.nursing_record TO 'nurse_user'@'localhost'; GRANT SELECT ON hospital_db.v_patient_bill TO 'nurse_user'@'localhost'; FLUSH PRIVILEGES;这里要强调的是权限粒度。医疗数据里,护士不应该看到费用明细,医生不应该写护理记录,这种细粒度授权正好呼应了“权限最小化”原则。答辩时如果老师问“为什么护士看不到患者费用”,你可以直接回答“护理角色没有费用相关表的授权,这种设计避免越权操作”。另外记得,GRANT之后必须FLUSH PRIVILEGES才能在新的连接里生效,这个FLUSH不是重载配置,而是让 MySQL 重新读一遍权限表。
5. 避坑指南:课设里最容易翻车的五个场景
这章是我每次带着学生调这套资源时总结出来的高频坑。每一条都来自实际运行报错或答辩现场的尴尬时刻,按“现象、原因、解决”三段写清楚。
5.1 中文写入后全部变成问号
现象:用图形工具导入02_base_data.sql后,科室名、护士姓名显示成“???”,但表结构里的中文注释正常。
原因:数据库、表、连接三个层的字符集不一致。最常见的是客户端连接时用了latin1,即使库表建成了utf8mb4,写入时也会被转成乱码。
解决:连接命令加--default-character-set=utf8mb4,然后在会话里执行SET NAMES utf8mb4。已经乱码的数据只能先删除再重新导入,不要试图用 UPDATE 修复,那基本是浪费时间。血泪经验:建库时一条DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci就能避开 90% 的乱码问题。
5.2 触发器更新自身表导致执行失败
现象:触发器逻辑是“病房可用床位减一”,但每次 UPDATEbed表后系统报错,提示无法在触发器中更新bed表,整笔操作直接回滚。
原因:我一开始把UPDATE ward错误写成了UPDATE bed,比如想通过触发器同步bed的另一个字段。MySQL 不允许在触发器里再次操作自身表,误认为触发了无限循环。
解决:把自我更新改成SET NEW.字段 = 值。特别注意NEW在 BEFORE 型触发器里可以直接改值,但在 AFTER 型触发器里NEW只读。跨表更新时,务必确认 UPDATE 语句操作的不是触发器自身所在的表。这个规则没有例外,遇到报错先检查触发体里有没有出现和 FOR EACH ROW 相同的表名。
5.3 存储过程中途提交导致事务失去作用
现象:sp_admit_patient在“插患者”之后、更新病床之前某个语句失败,调用结束后患者记录居然留在数据库里了。
原因:存储过程体内某处写了COMMIT,或者在调用存储过程前没有执行SET AUTOCOMMIT=0。MySQL 默认每一条 INSERT 自带提交,事务保护根本生效不了。
解决:检查存储过程体,COMMIT 只能出现在最后一个语句;事务开始前执行SET AUTOCOMMIT=0;异常处理部分只保留ROLLBACK和RESIGNAL,不要在 HANDLER 里写 COMMIT。改完后用“人为制造错误”验证:故意传入一个已被占用的p_bed_id,看看患者是否还被写入,如果写完被回滚,说明事务生效了。
5.4 删除病房时提示外键约束失败
现象:病房没患者了,想删掉一间空病房,但 DELETE 报错“Cannot delete or update a parent row”。
原因:这张病房下还有病床记录,病床表的外键引用了病房主键。即使病床状态是 AVAILABLE,也仍然是一条物理存在的子记录,RESTRICT 约束阻止删除。
解决:先删病床再删病房,顺序不能反过来。更符合业务的是不做物理删除,给ward加is_active字段,删的时候执行UPDATE ward SET is_active = 0 WHERE ward_id = ?,所有引用关系的完整性都不会被破坏。这个点也是答辩时的加分项:主动说明“医疗数据不能物理删,只能逻辑删”。
5.5 视图统计结果比预期大一倍
现象:查询v_patient_bill时发现某个患者的费用总额是实际金额的两倍。
原因:patient表 LEFT JOINcharge_record之后,如果charge_record里同一患者有多条记录,JOIN 结果行数会翻倍;此时如果SUM(c.amount)没有把重复行考虑进去,金额自然翻倍。
解决:先做子查询聚合,再关联患者主表:
CREATE OR REPLACE VIEW v_patient_bill_ok AS SELECT p.patient_id, p.name, c.charge_count, COALESCE(c.unpaid_amount, 0) AS unpaid_amount, COALESCE(c.total_amount, 0) AS total_amount FROM patient p LEFT JOIN ( SELECT patient_id, COUNT(*) AS charge_count, SUM(CASE WHEN status = 'UNPAID' THEN amount ELSE 0 END) AS unpaid_amount, SUM(amount) AS total_amount FROM charge_record GROUP BY patient_id ) c ON p.patient_id = c.patient_id;这个问题的根因是“先 JOIN 后聚合”和“先聚合后 JOIN”的差别。视图写法里优先选择后者,统计结果才稳定。另外,外连接要用COALESCE把 NULL 转成 0,否则视图里会显示空白金额,答辩时容易被追问。
6. 进阶验证:以“可答辩”为标准做四步收尾自检
资源里数据脚本导入完成、功能能跑,只算完成了一半。答辩时老师会现场点几个操作,所以我把“能跑”升级成“能当场演示不出错”。我现在每次拿到这类课设资源都会强制走一遍四步自检。
第一步是“脚本从头跑到尾”。删库重建,按01_schema.sql、02_base_data.sql、03_sample_data.sql的顺序重新执行一遍,全程记录错误输出。如果第二次执行还有报错,说明脚本幂等性不够,要么加了没有IF NOT EXISTS的建表语句,要么插入了重复主键,立刻改掉。
第二步是“业务链路连测”。按真实业务流程走一遍:先查空床,然后CALL sp_admit_patient写入一个测试患者,再给患者开一条医嘱,最后执行出院。出院这一步我一般会验证触发器是否同步更新了病房可用床位:
-- 入院前查询病房可用床位 SELECT ward_id, available_beds FROM ward WHERE ward_id = 1; -- 入院后再次查询 SELECT w.available_beds, SUM(CASE WHEN b.status = 'OCCUPIED' THEN 1 ELSE 0 END) AS actual_occupied FROM ward w JOIN bed b ON w.ward_id = b.ward_id WHERE w.ward_id = 1 GROUP BY w.ward_id, w.available_beds;两边数值对得上,说明触发器没漏执行。
第三步是“执行计划检查”。用EXPLAIN看核心查询是否走索引:
EXPLAIN SELECT * FROM medical_order WHERE patient_id = 5;如果type列显示ALL,说明这条查询没走索引,在patient_id上补一个普通索引即可。别小看这一步,老师现场随机报一个患者号查医嘱,如果查询秒出,印象分会高不少。
第四步是“备份恢复演练”。导出完整库再导入到另一备库,确认两边行数一致:
mysqldump -u root -p hospital_db > hospital_db_backup.sql mysql -u root -p -e "CREATE DATABASE hospital_db_test DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p hospital_db_test < hospital_db_backup.sql四步走完,这份资源里的代码你才算真正消化完。从那以后我每次拿到课设包都强制走一遍这套流程,已经少踩一半坑,希望帮到你。如果你手头正缺一套能直接跑的病房管理课设参考,这套资源值得下载后按上面的顺序重新验证一遍。
本文还有配套的精品资源,点击获取