ARTICLE DETAIL

资讯详情

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

健康档案管理系统MySQL数据库设计实战:从ER建模到事务部署

健康档案管理系统MySQL数据库设计实战:从ER建模到事务部署 简介一份完整的数据库课程设计项目报告围绕健康档案管理系统展开适合计算机相关专业学生用于数据库课程设计参考、答辩准备或系统设计入门。内容覆盖课程设计目的与意义、需求分析、数据流图与数据字典、概要结构设计、逻辑结构设计、物理结构设计等完整环节并在需求分析中给出学生健康信息管理的登记、修改、删除、查询、统计等功能框架以及体检文件、病历文件的数据字段要求。压缩包内仅含一份doc格式的Word文档大小约876KB可直接查看或编辑使用作为课程设计模板参考价值高。已有341人学习。通过这份报告可掌握从需求分析、数据字典编写到数据库结构设计的整体思路与方法适合需要完成类似课设或学习系统化数据库设计的学生借鉴。1. 数据库课程设计碰上健康档案管理系统先别急着建表数据库课程设计里健康档案管理系统是一个看着门槛低、实际容易失控的题目。很多人最初只想着“患者登录、医生录入”两张页面真正开始建库才发现健康档案不只是姓名电话还要跟体检记录、随访计划、诊断信息纠缠在一起实体一多外键和事务就藏不住了。这篇文章不是复制一份网上查重的模板而是按我实际做过的方案走一遍从需求边界、ER建模、MySQL建表、视图与存储过程到连上一个能演示的界面最后给出答辩前能跑通验证的脚本。适合正在做数据库课程设计 mysql 方向的读者做完这个健康档案管理系统既能解释清楚每张表的理由也扛得住老师追问。2. 健康档案管理系统的需求与实体建模患者、医生、体检记录为什么不拧成一团2.1 先画需求边界这题不是无限后台而是三个角色加四类业务记录健康档案管理系统如果照着医院 HIS 的规模去设计做到明年也做不完。课程设计考察的是数据库设计能力不是产品经理的想象力。我做这个题的第一件事不是写用户故事而是把名字里的「健康档案」拆成可落地的边界建档、就诊、体检、随访四件事。围绕这四件事角色只有三类——患者、医生、系统管理员。患者提供个人资料并接收归档建议医生在就诊时产生诊断内容管理员维护体检报告与随访计划。超出这个范围的功能比如预约挂号、药品库存、在线支付不是必须反而会把表拆到失去重点。这个边界和数据库结构直接对应。健康档案的一头是稳定的基础资料另一头是频繁产生的业务记录。基础资料包括患者身份信息、医生身份信息它们变化少适合独立成表业务记录包括每次就诊、每次体检和每次随访它们随时间增长必须把时间属性加进去。如果在建表阶段把“患者姓名”直接塞进就诊记录演示时看不出问题但遇到一个患者有多条就诊记录或改名数据就会失真。所以需求分析阶段就要决定哪些字段属于主数据哪些字段属于流水。选型上我用 MySQL 8.0。理由很现实查information_schema、写窗口函数ROW_NUMBER()、处理 JSON 导出都比 5.7 顺手而且课程设计环境装一个 MySQL Community 就够用。如果你学校指定的是 SQL Server关系模式可以原样迁移只有字符类型、TOP/LIMIT 和几个时间函数写法不同。NoSQL 在这个题目里不合适因为健康档案最大的价值是“查询一个患者历次体检变化轨迹”这种按主键关联和范围聚合正是关系数据库的强项。2.2 实体与关系五个实体和四条 1:N 关系实体太多会让答辩场面失控太少又显示不出设计能力。我常用五个实体患者 patient、医生 doctor、就诊记录 medical_record、体检记录 physical_exam、随访计划 follow_up。患者和医生是主数据就诊和体检是业务数据随访则用来体现“慢性病管理”这个健康档案特有场景。五个实体之间没有真正意义上的 N:M这是健康档案系统和选课系统的主要区别。以患者和医生为例一个患者可以被多位医生诊治一位医生也会诊治多个患者表面上像多对多。但健康档案建模时不要直接建 patient_doctor 中间表因为“诊治”不是静态隶属关系它发生在具体时间、伴随具体诊断和处方。所以我在中间放“就诊记录”实体medical_record 同时引用 patient_id 和 doctor_id把多对多转成了一个事实表加两个外键。这也是理解“为什么需要三张表而不是两张表”的关键。体检记录和患者则是一对多一个患者多年能做多次体检物理上一个体检记录只属于一个患者所以 physical_exam 里放 patient_id 外键就够了不需要中间表。实体确定后主键设计会影响后续所有 SQL。课程设计最常见的翻车点是拿身份证号做患者主键。身份证号是业务唯一键不是好的代理主键它可能在中途变更会暴露在查询接口里对极少数人来说也可能不是 18 位。所以我采用自增整数 patient_id 做主键id_card 单独加 UNIQUE 约束这样既能保证业务上不重复又能避免主键过长导致子表外键索引变大。医生表同样用自增 doctor_id。所有业务表的主键都用自增整数这是关系模式落到 MySQL 时成本最低的方案。2.3 范式与反范式为什么体检表里不存患者姓名很多同学在建表时会犹豫查询体检报告时总要显示患者姓名把 name 直接写在 physical_exam 里不是更方便吗这种做法叫反范式冗余在数据仓库里很常见但课程设计里的健康档案系统是 OLTP 场景数据一致性优先。患者改名是一个低频但合法的操作如果 name 冗余在体检表、就诊表、随访表多处一次 UPDATE 必须同步更新多处漏一处就是事故。我始终让 patient 表只保留一份姓名其他表通过 patient_id 外键去关联。范式上做到第三范式不存在非主属性对候选键的传递依赖。身份证号是候选键姓名、出生日期都由 patient_id 直接决定体检记录里只有身高、体重、血压这些由“本次体检”决定的数据患者姓名只依赖患者表不依赖体检记录。答辩时老师如果追问“你的设计满足第几范式”可以按这条线讲同时也要补一句到了统计报表阶段可以用视图反范式化而不是改表结构这样既满足范式要求也保证查询方便。3. 用 MySQL 建出健康档案系统的第一版从 ER 图到完整建表脚本3.1 一张 ER 图转成几张表记住“一”的键要下沉到“多”的表中ER 图不是画给老师看的它决定了以后每条 SQL 怎么走。我的经验是先把实体关系写成自然语言再定主题。健康档案系统里patient 和 physical_exam 是 1:Npatient 和 follow_up 是 1:Ndoctor 和 medical_record 是 1:N。关系模式转换规则每个实体一张表一对多关系在“多”方表中增加“一”方主键作外键多对多关系才需要单独建立中间表。当前系统没有单纯的多对多所以不需要额外中间表。字段分配时要区分“属于档案的信息”和“属于一次事件的信息”。患者的出生日期、血型、联系方式属于档案放在 patient身高、体重、血压属于某一次体检事件放在 physical_exam诊断文本、就诊日期属于某一次就诊事件放在 medical_record。如果因为“查询体检报告时想顺便看到患者姓名”就把 name 冗余到 physical_exam虽然查询少了一个 JOIN但患者改名时会产生不一致这是范式上的败笔。课程设计老师只要问一句“update patient 之后体检报告里的姓名为什么还是旧的”就会扣分。3.2 建库建表脚本字符集、引擎、外键顺序一次配齐MySQL 建表脚本是整篇课程设计能不能跑通的第一步。这里给出完整 SQL包含五个表患者、医生、就诊记录、体检记录、随访计划。注意建表顺序必须先创建 patient 和 doctor再创建引用它们的 medical_record、physical_exam、follow_up否则 MySQL 找不到引用表会直接报 1215 错误。-- 创建数据库直接指定 utf8mb4防止后面中文乱码 CREATE DATABASE IF NOT EXISTS health_archive DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE health_archive; -- 1. 患者主表 CREATE TABLE patient ( patient_id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 患者代理主键, id_card VARCHAR(18) NOT NULL COMMENT 身份证号业务唯一键, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT NOT NULL COMMENT 0-女 1-男, birth_date DATE NOT NULL COMMENT 出生日期, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, blood_type VARCHAR(4) DEFAULT NULL COMMENT 血型 A/B/AB/O, archive_status TINYINT NOT NULL DEFAULT 1 COMMENT 档案状态 1有效 0归档, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 建档时间, PRIMARY KEY (patient_id), UNIQUE KEY uk_patient_id_card (id_card) ) ENGINEInnoDB COMMENT健康档案患者主表; -- 2. 医生表 CREATE TABLE doctor ( doctor_id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 医生主键, name VARCHAR(50) NOT NULL COMMENT 姓名, department VARCHAR(50) NOT NULL COMMENT 科室, title VARCHAR(50) DEFAULT NULL COMMENT 职称, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, PRIMARY KEY (doctor_id), KEY idx_doctor_department (department) ) ENGINEInnoDB COMMENT医生表; -- 3. 就诊记录同时引用患者和医生是两者之间的事实表 CREATE TABLE medical_record ( record_id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 就诊记录主键, patient_id INT UNSIGNED NOT NULL COMMENT 患者ID, doctor_id INT UNSIGNED NOT NULL COMMENT 医生ID, visit_date DATE NOT NULL COMMENT 就诊日期, complaint VARCHAR(255) DEFAULT NULL COMMENT 主诉症状, diagnosis TEXT COMMENT 诊断结果, treatment TEXT COMMENT 处理意见, PRIMARY KEY (record_id), KEY idx_record_patient (patient_id), KEY idx_record_doctor (doctor_id), CONSTRAINT fk_record_patient FOREIGN KEY (patient_id) REFERENCES patient (patient_id) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT fk_record_doctor FOREIGN KEY (doctor_id) REFERENCES doctor (doctor_id) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINEInnoDB COMMENT就诊记录表; -- 4. 体检记录每次体检产生一条外键指向患者 CREATE TABLE physical_exam ( exam_id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 体检记录主键, patient_id INT UNSIGNED NOT NULL COMMENT 患者ID, exam_date DATE NOT NULL COMMENT 体检日期, height_cm DECIMAL(5,1) DEFAULT NULL COMMENT 身高(cm), weight_kg DECIMAL(5,2) DEFAULT NULL COMMENT 体重(kg), systolic INT UNSIGNED DEFAULT NULL COMMENT 收缩压 mmHg, diastolic INT UNSIGNED DEFAULT NULL COMMENT 舒张压 mmHg, heart_rate INT UNSIGNED DEFAULT NULL COMMENT 心率 次/分, exam_notes TEXT COMMENT 体检小结, PRIMARY KEY (exam_id), KEY idx_exam_patient (patient_id), CONSTRAINT fk_exam_patient FOREIGN KEY (patient_id) REFERENCES patient (patient_id) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINEInnoDB COMMENT体检记录表; -- 5. 随访计划体现慢性病管理仍与患者关联 CREATE TABLE follow_up ( follow_up_id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 随访主键, patient_id INT UNSIGNED NOT NULL COMMENT 患者ID, plan_date DATE NOT NULL COMMENT 计划随访日期, result VARCHAR(255) DEFAULT NULL COMMENT 随访结果, done TINYINT NOT NULL DEFAULT 0 COMMENT 0未完成 1已完成, PRIMARY KEY (follow_up_id), KEY idx_followup_patient (patient_id), CONSTRAINT fk_followup_patient FOREIGN KEY (patient_id) REFERENCES patient (patient_id) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINEInnoDB COMMENT随访计划表;建完表后插入测试数据注意先插父表 patient 和 doctor再插有外键的子表否则找不到父记录会报 1452 错误。INSERT INTO patient (id_card, name, gender, birth_date, phone, blood_type) VALUES (110101198503124215, 张伟, 1, 1985-03-12, 13800000001, A), (110101199207063146, 李娜, 0, 1992-07-06, 13800000002, B); INSERT INTO doctor (name, department, title, phone) VALUES (王建国, 全科, 副主任医师, 13900000001); INSERT INTO medical_record (patient_id, doctor_id, visit_date, complaint, diagnosis, treatment) VALUES (1, 1, 2025-04-10, 头晕乏力, 轻度高血压, 低盐饮食注意休息), (2, 1, 2025-04-11, 咽痛咳嗽, 上呼吸道感染, 多饮水必要时复诊); INSERT INTO physical_exam (patient_id, exam_date, height_cm, weight_kg, systolic, diastolic, heart_rate) VALUES (1, 2025-04-10, 172.0, 76.50, 142, 92, 76), (2, 2025-04-11, 160.0, 52.10, 118, 70, 72); INSERT INTO follow_up (patient_id, plan_date, result, done) VALUES (1, 2025-05-01, NULL, 0);这段 SQL 的关键参数要从两个角度看。字符集选utf8mb4_unicode_ci因为 GBK 只能在中文场景用utf8mb4 才支持再往后的扩展符号引擎必须写InnoDB因为只有 InnoDB 支持外键和事务MyISAM 在这两个核心需求上直接报废。外键的ON UPDATE CASCADE允许主键变更时级联更新但自增主键实际很少触发ON DELETE RESTRICT表示子表存在数据时禁止物理删除这是健康档案领域的硬要求。另外height_cm DECIMAL(5,1)的意思是总长度 5 位、小数 1 位最大能存 999.9 厘米一般人用不到这个上限体重DECIMAL(5,2)最大 999.99 千克别用 FLOAT避免浮点比较误差。3.3 字段类型与约束五个最容易被追问的细节第一身份证号必须用VARCHAR(18)。18 位数字已经超过INT的 4 字节上限用BIGINT虽然放得下但身份证号里有 X 结尾的情况纯数字类型存不了更重要的是身份证号不做算术运算字符串存储是语义正确的选择。第二性别和档案状态我用TINYINT而不是ENUM。ENUM在 MySQL 里可读性好但后续如果业务方要求把性别改成 0/1/2枚举要变更元数据用 TINYINT 加注释配合视图里的CASE WHEN转中文是更稳的习惯。第三日期字段分两种就诊日期、体检日期、随访计划日期用DATE只精确到天建档时间用DATETIME DEFAULT CURRENT_TIMESTAMP自动记录到秒。MySQL 5.7 里 DATETIME 不能用 CURRENT_TIMESTAMP 作为默认值时需要加时间戳字段8.0 已经放开这也是我推荐 8.0 的原因之一。第四外键列必须建索引。MySQL 会自动为外键建索引但显式写KEY idx_record_patient (patient_id)能让EXPLAIN更可控也避免视图 JOIN 时出现 Using filesort。第五archive_status是逻辑删除字段不是真的把档案从库里删掉。医疗数据有追溯要求所以删除患者这个操作默认会被外键拦截正确姿势是把状态改成 0查询时统一过滤掉。4. 给健康档案系统加数据库灵魂视图、存储过程与事务的落地用法4.1 存储过程做“建档或更新”减少重复代码并保持业务规则一致在课程设计中如果只给一张表做增删改查老师会觉得像管理系统而不是数据库设计。我通常会在 patient 表上加一个upsert_patient存储过程把“按身份证号建档存在则更新”的业务规则收进数据库。这样外部任何入口不管是 JSP 还是 Python都调用同一个过程不会出现有人 INSERT、有人 UPDATE 造成的重复档案。DELIMITER $$ DROP PROCEDURE IF EXISTS upsert_patient$$ CREATE PROCEDURE upsert_patient( p_id_card VARCHAR(18), p_name VARCHAR(50), p_gender TINYINT, p_birth_date DATE, p_phone VARCHAR(20), p_blood_type VARCHAR(4) ) BEGIN IF EXISTS (SELECT 1 FROM patient WHERE id_card p_id_card) THEN UPDATE patient SET name p_name, gender p_gender, birth_date p_birth_date, phone p_phone, blood_type p_blood_type WHERE id_card p_id_card; ELSE INSERT INTO patient (id_card, name, gender, birth_date, phone, blood_type) VALUES (p_id_card, p_name, p_gender, p_birth_date, p_phone, p_blood_type); END IF; END$$ DELIMITER ;这里用了DELIMITER $$因为 MySQL 命令行客户端默认以分号作为语句分隔符而存储过程内部包含分号必须临时把分隔符改成$$结束后再改回分号。参数名统一加p_前缀避免和列名name冲突。IF EXISTS判断的是业务唯一键id_card不是主键patient_id这样外部调用者不用关心自增值。如果想返回新生成的 patient_id可以再写一个OUT参数但课程设计往往只要求“能调用成功”我建议别加太多 OUT 参数否则答辩时需要解释更多。调用测试语句如下第一次调用后记录被更新但 patient_id 不变若传一个新身份证号则插入新记录。CALL upsert_patient(110101198503124215, 张伟, 1, 1985-03-12, 13800000001, A); SELECT patient_id, name, blood_type FROM patient WHERE id_card 110101198503124215;存储过程的优点是业务规则收敛缺点是调试困难、版本管理不方便所以不要在过程里堆太多逻辑。健康档案系统里一个事实表配一个核心过程就够了比如再写一个insert_exam_proc校验体检日期不能早于患者建档日期现场演示给老师看比在页面里做一堆 if 判断更有数据库味道。4.2 视图把多表 JOIN 变成一张“虚表”供报表使用视图的本质是一次命名查询不占额外存储空间。健康档案系统的报表页面如果每次都写四个表 JOIN代码又长又容易漏。我建了一个v_patient_full把患者、就诊、医生、体检信息拼好前端只需要查询这一张视图。CREATE OR REPLACE VIEW v_patient_full AS SELECT p.patient_id, p.name AS patient_name, p.gender, p.birth_date, p.blood_type, r.visit_date, r.diagnosis, d.department, e.exam_date, e.systolic, e.diastolic FROM patient p LEFT JOIN medical_record r ON p.patient_id r.patient_id LEFT JOIN doctor d ON r.doctor_id d.doctor_id LEFT JOIN physical_exam e ON p.patient_id e.patient_id;这里用LEFT JOIN而不是INNER JOIN因为刚建档的患者可能还没有就诊记录和体检记录用INNER JOIN会被过滤掉导致档案列表少人。视图能起到权限封装的作用应用账号可以只拥有读视图的权限而不直接暴露物理表老师问到“为什么用视图”时可以这样答。但也要注意性能视图嵌套太深会让 EXPLAIN 变得难看这个视图只是演示报表查询不要直接在视图上再做复杂 GROUP BY而应该先确认有没有行数放大。下面这条查询可以用于演示“血压异常患者按性别统计”注意COUNT(DISTINCT patient_id)防止一个患者多次体检被重复计数。SELECT CASE WHEN gender 1 THEN 男 ELSE 女 END AS gender, COUNT(DISTINCT patient_id) AS abnormal_cnt, AVG(systolic) AS avg_systolic FROM v_patient_full WHERE systolic 140 GROUP BY gender;4.3 事务一次“体检 随访计划”必须同时成功健康档案系统最担心产生“有体检记录但没有随访计划”的中间态。比如患者今天做完体检医生根据血压决定两周后随访这两个动作如果第一步插入成功、第二步失败数据就前后对不上。MySQL 的 InnoDB 提供事务需要把这两步放到一个原子操作里。START TRANSACTION; INSERT INTO physical_exam (patient_id, exam_date, height_cm, weight_kg, systolic, diastolic, heart_rate) VALUES (1, 2025-04-20, 171.5, 75.20, 138, 88, 78); UPDATE follow_up SET result 血压仍偏高继续观察, done 1 WHERE patient_id 1 AND plan_date 2025-05-01; COMMIT;执行到任意一条语句出错应用层可以ROLLBACK前面已执行的变更会被撤销。初学者最容易漏写COMMIT因为 MySQL 客户端默认 autocommit1但手动START TRANSACTION之后必须显式COMMIT否则连接关闭时事务回滚等于白写。还要知道 MySQL 默认隔离级别是REPEATABLE READ在并发写同一个患者时InnoDB 会对被更新的行加锁另一个连接更新同一行会等待。课程设计如果有余力可以开两个 MySQL 客户端演示这种行锁现象一句“这是写入锁带来的阻塞”就能让答辩分高出不少。5. 部署与避坑实战健康档案系统连不上 MySQL 时的排查手册5.1 最小可演示的连库方案Flask PyMySQL 跑通患者列表查询课程设计不一定写完整前端但至少要有一个能操作的界面。我常用的最小方案是 Flask PyMySQL一个文件就能跑不需要装 JDK也不用配 Tomcat如果老师要求 Java Web你照着同样的 SQL 改成 JDBC 的 PreparedStatement 即可。先装依赖pip install flask pymysql cryptographycryptography是 PyMySQL 连接 MySQL 8 时处理caching_sha2_password认证插件需要的不装会报Authentication plugin cannot be loaded。下面是最小启动程序实现患者列表和就诊次数统计。from flask import Flask, render_template_string import pymysql app Flask(__name__) DB_CONFIG { host: 127.0.0.1, port: 3306, user: health_user, password: Health2024, database: health_archive, charset: utf8mb4, cursorclass: pymysql.cursors.DictCursor, } app.route(/) def index(): conn pymysql.connect(**DB_CONFIG) try: with conn.cursor() as cursor: cursor.execute( SELECT p.name, p.gender, p.birth_date, p.phone, COUNT(r.record_id) AS visit_cnt FROM patient p LEFT JOIN medical_record r ON p.patient_id r.patient_id GROUP BY p.patient_id ORDER BY p.patient_id ) rows cursor.fetchall() finally: conn.close() return render_template_string( h2健康档案管理系统 - 患者列表/h2 table border1 trth姓名/thth性别/thth出生日期/thth电话/thth就诊次数/th/tr {% for row in rows %} tr td{{ row[name] }}/td td{{ 男 if row[gender] 1 else 女 }}/td td{{ row[birth_date] }}/td td{{ row[phone] }}/td td{{ row[visit_cnt] }}/td /tr {% endfor %} /table , rowsrows) if __name__ __main__: app.run(host127.0.0.1, port5000, debugTrue)DB_CONFIG的关键参数含义host必须和 MySQL 的监听地址匹配默认 127.0.0.1 即可charsetutf8mb4保证取出的中文不乱码DictCursor让查询结果以字典形式返回模板里直接写row[name]可读性好。注意每次请求都要conn.close()否则页面多刷新几次 MySQL 会报Too many connections。我一般用 try/finally 而不是 with conn 关连接因为旧版 PyMySQL 的 with 行为只关游标不关连接。如果老师要求 Java Web 方向连接串改成jdbc:mysql://127.0.0.1:3306/health_archive?useUnicodetruecharacterEncodingutf8useSSLfalseSQL 不需要变这也是前面坚持用标准 SQL 写视图和事务的原因。5.2 避坑排查四个现场故障现象、原因、解决第一个常见故障Flask 运行后提示pymysql.err.OperationalError: (1045, Access denied for user rootlocalhost)。原因是 MySQL 密码不对或者 root 账号默认只允许本机 socket 登录。很多学生安装 MySQL 时设的 root 密码带特殊字符写进 Python 字符串没有转义还有的机器 root 用了 auth_socket 插件命令行不用密码能登录但程序连不上。解决方式不要在程序里硬怼 root在 MySQL 里建一个应用专用账号并授权。CREATE USER health_userlocalhost IDENTIFIED BY Health2024; GRANT SELECT, INSERT, UPDATE, DELETE ON health_archive.* TO health_userlocalhost; FLUSH PRIVILEGES;第二个常见故障用命令行导入 SQL 时报1366 Incorrect string value: \xE5... for column name中文全部变成问号。原因是数据库默认字符集是 latin1或者导入会话的字符集不是 utf8mb4。解决方式建库时明确指定DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci导入前先执行SET NAMES utf8mb4;。如果表已经建错了用ALTER TABLE patient CONVERT TO CHARACTER SET utf8mb4;一次性改全表字段的字符集。第三个常见故障从前端界面删除某位已经做过体检的患者MySQL 报Cannot delete or update a parent row: a foreign key constraint fails。这个现象其实是数据库在保护数据不是系统坏了。原因是 medical_record 和 physical_exam 的外键都设了ON DELETE RESTRICT业务上不允许物理删除有历史记录的患者。解决方式不要用 DELETE而是用UPDATE patient SET archive_status 0 WHERE patient_id ?把“删除”改成“归档”。如果真要物理删除必须先把子表数据迁移或删除。答辩时主动说“医疗记录有追溯要求我不允许直接物理删除”这句话非常加分。第四个常见故障用浏览器新增患者后刷新列表发现中文全是问号。原因是网页模板的 meta charset 没设置或者 Python 源文件在 Windows 编辑器里被保存成了 GBK。解决方式模板里加meta charsetutf-8编辑器右下角把文件编码改成 UTF-8数据库连接参数已经加 charsetutf8mb4。这个坑很“玄学”因为在 PyMySQL 里连接编码对了但 HTML 页面编码不对照样乱的像摩斯密码。6. 答辩当天的验收脚本与进阶改进用三个 SQL 让老师相信系统是真的6.1 一分钟验收脚本数据完整性、外键、统计口径在演示前我不会只靠眼睛看页面而是直接跑一遍 SQL返回结果正常才敢上台。下面三条是我固定会跑的mysql -u health_user -pHealth2024 health_archive \ -e SELECT COUNT(*) AS patient_cnt FROM patient; mysql -u health_user -pHealth2024 health_archive \ -e SELECT COUNT(*) FROM physical_exam WHERE systolic 140; mysql -u health_user -pHealth2024 health_archive \ -e SELECT id_card, COUNT(*) FROM patient GROUP BY id_card HAVING COUNT(*) 1;命令参数解释-pHealth2024紧跟 -p 表示密码这里用单引号是为了避免 shell 解析特殊字符。第一条确认基础表有数据第二条验证健康指标可以被统计第三条如果返回空说明 id_card 的唯一索引生效。这三条跑完老师再问“你这个系统能应对真实数据吗”你可以直接指着终端说表结构注册过外键业务数据也按唯一索引校验过。6.2 如果时间还有富余加权限、加归档、加导出三个低成本改进能明显提升完成度。第一把前端连库账号改成只读账号写入操作走存储过程第二所有查询默认带archive_status 1已归档档案不混在列表中第三用 Flask 加一个导出 CSV 的接口或者用SELECT ... INTO OUTFILE导出健康数据满足“档案可迁移”的追问。这三项不需要改表结构但能回答“为什么你的系统能落地”。我吃过一次亏答辩前一天发现 root 密码里有和#Flask 连接字符串一直报错后来全部换成专用账号才解决。所以现在无论项目大小我都会单独建账号并在最后一天重跑一遍上面的验证脚本。希望帮到你。本文还有配套的精品资源点击获取
返回列表