ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

医院病房管理系统数据库设计:从建表到并发事务的完整实践

医院病房管理系统数据库设计:从建表到并发事务的完整实践 简介这份资源是面向高校计算机相关专业学生的数据库课程设计完整项目包以医院病房管理系统为业务场景帮助读者完成从需求分析到系统落地的全流程实践。项目围绕病人信息、病房床位、医护人员排班等核心实体展开涵盖ER建模、主外键与索引设计、视图与SQL语句编写并涉及B/S架构下的前后端交互与业务逻辑处理。压缩包共146个文件约5.04MB以Java源码与编译后的class文件为主体辅以jpg、png界面截图、xml配置、sql建库脚本及doc说明文档结构完整便于直接运行与二次修改。目前已有164人学习下载。读者可据此获得一套可参考的数据库设计思路、表结构定义与功能模块划分方案理解入院登记、病房分配、费用结算等业务流程如何映射为数据操作并借鉴权限控制与数据备份等安全设计适合作为课设模板或数据库综合练习的对照案例。1. 从一份课设压缩包说起医院病房管理系统到底要解决什么医院病房管理系统这个题目几乎每年都会出现在数据库课程设计的选题清单里。很多同学拿到它第一反应是不就是增删改查吗然后花两天把表建完、界面拖完交上去发现分数不高——问题往往不在功能多少而在数据模型有没有真正贴合病房业务。病房管理的核心矛盾是床位是有限资源病人是流动对象医护是排班约束三者交叉产生大量状态变更而这些状态变更必须可追溯、可并发、不能出现一张床同时住两个人这种脏数据。这份课设要落地的东西其实很明确用一张床位表串起入院、转床、出院的全生命周期用医嘱和护理记录表承载诊疗过程用排班表约束医护资源。它适合正在做数据库课设的在校生也适合想拿一个完整业务场景练手 SQL 建模、事务控制和并发锁的初学者。热词里反复出现的数据库增删改查数据库课程设计数据库并发锁恰好对应这个系统的三个层次基础 CRUD、业务建模、并发一致性。下面我按自己带课设和做实际项目的经验把这份东西从建表到跑通讲清楚。2. 病房管理系统的表结构怎么设计才不返工2.1 先画状态机再动手建表绝大多数返工都源于跳过状态梳理直接建表。病房业务里一张床位至少有五种状态空闲、已预约、占用、待清洁、停用。病人至少有四种状态待入院、在院、待出院、已出院。如果你只建一张bed表加一个status字段很快就会发现病人转床这个操作要同时改两张表而且中间任何一步失败都会导致数据不一致。我一般会先把状态迁移画成文字版入院时床位从空闲变占用、病人从待入院变在院转床时旧床变待清洁、新床从空闲变占用出院时床位变待清洁、病人变已出院。把这条链路写清楚表结构自然就出来了——床位表、病人表、床位占用记录表三张表分离占用记录表专门记录谁在什么时间段占了哪张床这样历史可追溯转床也只是插入一条新记录加更新旧记录。2.2 核心建表语句与字段说明下面是我常用的最小可用表结构MySQL 8.0 语法其他数据库改一下自增和引擎即可。-- 病房表物理房间 CREATE TABLE ward ( ward_id INT PRIMARY KEY AUTO_INCREMENT, ward_name VARCHAR(32) NOT NULL, -- 如内科一病区 floor_no INT NOT NULL, dept_id INT NOT NULL, -- 所属科室 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 床位表核心资源 CREATE TABLE bed ( bed_id INT PRIMARY KEY AUTO_INCREMENT, ward_id INT NOT NULL, bed_no VARCHAR(16) NOT NULL, -- 如03-12 bed_status TINYINT NOT NULL DEFAULT 0, -- 0空闲 1预约 2占用 3待清洁 4停用 bed_type TINYINT NOT NULL DEFAULT 0, -- 0普通 1监护 2隔离 UNIQUE KEY uk_ward_bed (ward_id, bed_no), CONSTRAINT fk_bed_ward FOREIGN KEY (ward_id) REFERENCES ward(ward_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 病人表 CREATE TABLE patient ( patient_id INT PRIMARY KEY AUTO_INCREMENT, id_card VARCHAR(18) NOT NULL, name VARCHAR(32) NOT NULL, gender TINYINT NOT NULL, phone VARCHAR(20), pat_status TINYINT NOT NULL DEFAULT 0, -- 0待入院 1在院 2待出院 3已出院 UNIQUE KEY uk_idcard (id_card) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 床位占用记录可追溯的核心 CREATE TABLE bed_occupancy ( occ_id BIGINT PRIMARY KEY AUTO_INCREMENT, bed_id INT NOT NULL, patient_id INT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, -- NULL 表示仍在占用 occ_status TINYINT NOT NULL DEFAULT 1, -- 1占用中 2已释放 KEY idx_bed_time (bed_id, start_time), KEY idx_patient (patient_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明bed表存当前状态用于快速查询bed_occupancy存历史用于审计和对账。两者通过业务逻辑保证一致而不是靠外键——因为当前状态和历史记录是两种语义硬绑外键反而限制转床操作。参数上bed_status用 TINYINT 而不是 ENUM是为了后续加状态不用改表结构end_time允许 NULL 是转床和出院场景的关键NULL 代表这条占用还没结束。2.3 医嘱、护理与排班表的取舍课设里常见的坑是把医嘱、护理记录、排班全塞进一张大表。我的做法是拆开医嘱表medical_order关联病人和开单医生护理记录表nursing_record关联病人和护士排班表duty_schedule关联医护和班次。三张表都只存业务字段不冗余病人姓名——需要展示时 JOIN 查询。这样做的代价是查询要多写 JOIN收益是数据不会因为病人改名而出现不一致。热词里的数据库优化在这个场景下第一步就是消除冗余字段而不是急着加索引。3. 入院、转床、出院三个事务怎么写才不出脏数据3.1 入院事务先锁床再插记录入院是最容易出并发的操作。两个护士同时给同一个病人分配同一张床如果不加锁就会出现两条占用记录指向同一张床。正确顺序是开启事务 → 用SELECT ... FOR UPDATE锁住目标床位行 → 检查状态是否为空闲 → 更新床位状态 → 插入占用记录 → 更新病人状态 → 提交。START TRANSACTION; -- 锁定目标床位防止并发抢占 SELECT bed_status FROM bed WHERE bed_id 101 FOR UPDATE; -- 应用层判断若 bed_status ! 0 则回滚 UPDATE bed SET bed_status 2 WHERE bed_id 101 AND bed_status 0; -- 若上一步 affected_rows 0说明被抢回滚 INSERT INTO bed_occupancy (bed_id, patient_id, start_time, occ_status) VALUES (101, 2001, NOW(), 1); UPDATE patient SET pat_status 1 WHERE patient_id 2001; COMMIT;逻辑说明FOR UPDATE是行级锁锁的是bed_id101这一行不影响其他床位。UPDATE语句里带AND bed_status 0是双重保险——即使锁没生效条件更新也能保证只有一条成功。参数上affected_rows是判断成败的关键很多同学只看有没有报错忽略了更新了 0 行也是一种失败。热词里的数据库并发锁数据库死锁在这个场景下就是靠行锁加固定加锁顺序来规避所有涉及多张表的操作都按 bed → occupancy → patient 的顺序加锁避免交叉等待。3.2 转床事务旧床释放与新床占用必须原子转床比入院复杂因为它涉及两张床位。血泪经验是不要先释放旧床再占用新床中间任何一步失败都会让病人无床可归。正确做法是在一个事务里同时处理并且加锁顺序按bed_id从小到大防止两个转床操作互相等待造成死锁。START TRANSACTION; -- 按 bed_id 升序加锁避免死锁 SELECT bed_id, bed_status FROM bed WHERE bed_id IN (101, 205) ORDER BY bed_id FOR UPDATE; -- 释放旧床占用记录 UPDATE bed_occupancy SET end_time NOW(), occ_status 2 WHERE bed_id 101 AND patient_id 2001 AND occ_status 1; -- 旧床转待清洁 UPDATE bed SET bed_status 3 WHERE bed_id 101; -- 新床占用 UPDATE bed SET bed_status 2 WHERE bed_id 205 AND bed_status 0; -- 新占用记录 INSERT INTO bed_occupancy (bed_id, patient_id, start_time, occ_status) VALUES (205, 2001, NOW(), 1); COMMIT;逻辑说明ORDER BY bed_id FOR UPDATE是防死锁的标准手法两个并发转床事务如果都按同一顺序加锁就不会出现 A 等 B、B 等 A 的循环。参数上occ_status 1这个条件不能省它保证只释放仍在占用的记录避免重复释放。如果新床更新影响行数为 0说明新床已被占整个事务回滚旧床状态自动恢复——这就是事务的后悔药。3.3 出院事务与床位清洁流转出院本身不复杂但很多课设漏了待清洁这个中间态。病人出院后床位不能立即变空闲必须经过清洁确认。所以出院事务只做三件事结束占用记录、病人状态改已出院、床位状态改待清洁。清洁完成是另一个独立操作由后勤角色触发把床位从待清洁改回空闲。这样设计的好处是床位状态和真实物理状态一致不会出现系统显示空闲但床上还躺着上一位病人的东西。START TRANSACTION; UPDATE bed_occupancy SET end_time NOW(), occ_status 2 WHERE patient_id 2001 AND occ_status 1; UPDATE patient SET pat_status 3 WHERE patient_id 2001; UPDATE bed SET bed_status 3 WHERE bed_id (SELECT bed_id FROM bed_occupancy WHERE patient_id 2001 ORDER BY start_time DESC LIMIT 1); COMMIT;逻辑说明子查询取最近一条占用记录的床位确保改的是正确的床。参数上如果病人有多次转床历史ORDER BY start_time DESC LIMIT 1能拿到当前床位。这里要注意 MySQL 不允许在 UPDATE 中直接子查询同一张表实际写的时候要么用变量先存 bed_id要么用 JOIN 改写这是课设里高频翻车点。4. 查询与统计护士站大屏和日报表怎么出数4.1 实时床位看板查询护士站最常看的是每个病区当前空闲、占用、待清洁各多少张床。这个查询要避免全表扫描靠bed_status和ward_id的联合索引。-- 建议索引 ALTER TABLE bed ADD INDEX idx_ward_status (ward_id, bed_status); -- 病区床位状态汇总 SELECT w.ward_name, SUM(CASE WHEN b.bed_status 0 THEN 1 ELSE 0 END) AS free_cnt, SUM(CASE WHEN b.bed_status 2 THEN 1 ELSE 0 END) AS occupied_cnt, SUM(CASE WHEN b.bed_status 3 THEN 1 ELSE 0 END) AS cleaning_cnt FROM ward w LEFT JOIN bed b ON b.ward_id w.ward_id GROUP BY w.ward_id, w.ward_name;逻辑说明用CASE WHEN做条件聚合一次查询出三种状态比跑三次 COUNT 高效。参数上idx_ward_status让 GROUP BY 能走索引数据量大时差别明显。热词里的数据库sql查询数据库落到这里就是别写SELECT *只取需要的列。4.2 病人住院天数与床位周转率统计课设答辩常被问你这个系统能出什么报表。住院天数和床位周转率是两个必答项。-- 各病区本月床位周转率 出院人数 / 平均开放床位数 SELECT w.ward_name, COUNT(DISTINCT o.patient_id) AS discharged_cnt, COUNT(DISTINCT b.bed_id) AS bed_cnt, ROUND(COUNT(DISTINCT o.patient_id) / COUNT(DISTINCT b.bed_id), 2) AS turnover_rate FROM ward w JOIN bed b ON b.ward_id w.ward_id LEFT JOIN bed_occupancy o ON o.bed_id b.bed_id AND o.occ_status 2 AND o.end_time 2024-01-01 AND o.end_time 2024-02-01 GROUP BY w.ward_id, w.ward_name;逻辑说明周转率的分母用开放床位数而非总床位数更贴近实际管理口径。参数上时间范围用左闭右开 AND 避免边界重复统计。如果数据量大bed_occupancy的end_time上要单独建索引否则这个查询会拖垮库。4.3 医嘱与护理记录的关联查询医生查房时要看某病人当前有效医嘱和最近护理记录。这类查询的关键是只取有效数据别把历史作废医嘱也带出来。SELECT p.name, mo.order_content, mo.create_time, nr.record_content, nr.record_time FROM patient p LEFT JOIN medical_order mo ON mo.patient_id p.patient_id AND mo.order_status 1 LEFT JOIN nursing_record nr ON nr.patient_id p.patient_id AND nr.record_time (SELECT MAX(record_time) FROM nursing_record WHERE patient_id p.patient_id) WHERE p.patient_id 2001;逻辑说明order_status 1过滤有效医嘱护理记录用子查询取最近一条。参数上如果护理记录表很大子查询会慢可以改成窗口函数ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY record_time DESC)MySQL 8.0 支持。这是数据库优化从会写到会调的分水岭。5. 课设里最容易翻车的五个坑5.1 现象转床后旧床一直显示占用原因只更新了bed_occupancy的end_time忘了把bed.bed_status改回待清洁。两张表状态不同步。 解决把床位状态更新和占用记录更新放进同一个事务任何一步失败一起回滚。写完后用一条对账 SQL 定期检查SELECT b.bed_id FROM bed b LEFT JOIN bed_occupancy o ON o.bed_id b.bed_id AND o.occ_status 1 WHERE b.bed_status 2 AND o.occ_id IS NULL;有结果就说明状态不一致。5.2 现象并发测试时出现死锁报错原因两个事务加锁顺序相反一个先锁床 101 再锁 205另一个反过来。 解决所有多行加锁操作统一按主键升序用ORDER BY bed_id FOR UPDATE。死锁无法完全消除但可以大幅降低概率。应用层要捕获死锁异常并重试一次这是标准做法。5.3 现象病人身份证号重复插入成功原因只在应用层查重没建唯一索引并发下两个请求同时通过检查。 解决id_card上必须建UNIQUE KEY让数据库兜底。应用层捕获唯一键冲突异常提示该病人已存在。热词里的mysql设置唯一已经有重复数据库说的就是这种情况——建唯一索引前要先清理历史重复数据否则索引建不上。5.4 现象统计报表数字对不上原因bed_occupancy里存在end_time为 NULL 但occ_status已经是 2 的脏数据或者反过来。 解决加约束或触发器保证occ_status 2时end_time不为 NULL。课设里可以用 CHECK 约束MySQL 8.0.16 支持CHECK ((occ_status 1 AND end_time IS NULL) OR (occ_status 2 AND end_time IS NOT NULL))。5.5 现象换台电脑跑不起来原因建表脚本里用了某个数据库特有的语法或者字符集没指定导致中文乱码。 解决建表统一带DEFAULT CHARSETutf8mb4避免用数据库特有函数。导出脚本时用mysqldump --no-data只导结构配合单独的初始化数据脚本别人拿到就能跑。热词里的sqllite数据库达梦数据库如果要用注意自增语法和FOR UPDATE支持程度不同SQLite 没有行级锁并发场景要换方案。6. 把课设做成能写进简历的项目三个进阶方向课设拿高分只是及格线真正值钱的是把它做出工程味。第一个方向是加审计日志所有状态变更写一张audit_log表记录操作人、操作时间、变更前后值。实现方式可以用应用层拦截也可以用数据库触发器。触发器写法简单但难维护我一般用应用层 AOP 或手动埋点字段至少包含table_name、record_id、old_value、new_value、operator、op_time。这样答辩时被问数据被改了怎么查你能直接演示。第二个方向是做读写分离的雏形。课设数据量小但可以手动模拟统计查询走从库连接事务操作走主库。MySQL 主从配置在单机上就能搭用两个实例不同端口即可。热词里的数据库同步软件数据库同步工具在这个场景下就是主从复制的落地核心是理解 binlog 和 relay log 的流转。哪怕只配通一次你对先写数据库还是先写 MQ这类问题就有了实感。第三个方向是接口层加缓存。床位看板这种高频只读查询可以用 Redis 缓存设置 5 到 10 秒过期。关键是缓存更新策略状态变更时主动删缓存而不是更新缓存避免并发写导致脏数据。验证方法很简单用ab或wrk压测看板接口对比加缓存前后的 QPS。进阶方向核心表/组件验证方式简历一句话审计日志audit_log改一条数据查日志实现全量状态变更可追溯读写分离主从实例从库延迟监控搭建 MySQL 主从并分流统计查询接口缓存Redis压测 QPS 对比热点查询缓存化QPS 提升可量化最后说个习惯我做完任何课设都会写一个reset.sql一键清库重建加灌测试数据。这样每次改表结构不用手动删表也方便演示。带过的学生里凡是坚持写这个脚本的答辩时都从容得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表