简介:Wi6.5数据库(随心所欲XP工作站医疗数据库)面向医疗信息化开发者、维护人员及需要了解XP工作站数据库结构的学习者,围绕患者信息、诊疗记录等敏感数据的管理需求,提供一套基于Interbase 6.5的工作站数据管理实例。压缩包为rar格式,共967个文件、约28.43MB,包含pas、dfm、dpr等Delphi源码与窗体文件,sql、gdb、qry等数据库脚本和库文件,以及exe、dll、hlp、html、txt等可执行程序、帮助文档与说明资料,便于从代码、界面到数据库层对照理解。包内还可见rpt报表、java/jar组件及配置文件,能辅助梳理医疗业务数据的组织方式、查询流程和部署结构。已有1004人学习,适合用来研究Interbase 6.5在医疗场景下的表结构设计、权限控制、备份恢复与工作站应用实现,也可作为旧系统维护和迁移分析的参考样本。
1. 一台还在跑 XP 的收费工作站:wi6.5 数据库到底在解决什么问题
上个月帮人看一台门诊收费窗口的机器,开机进系统就弹窗,收费员只能先手写小票。机器是 XP,屏幕分辨率还是 1024×768,硬盘响得像老式风扇,可它上面跑的那套 wi6.5 数据库里存着整个门诊的患者主索引、就诊流水、收费明细和药品字典。这类 wi6.5 数据库说白了就是给 XP 工作站配套的本地医疗数据库,装机即用、不需要单独买服务器,名字里那个“随心所欲”大概也是这个意思——一台工作站就是一个完整的数据节点。
它解决的问题很具体:老设备不能换、业务不能停、数据不能丢。适合三类人看:负责基层医疗点运维的工程师、接手老系统还要做数据迁移的开发者、需要把历史收费数据导出对账的财务信息化人员。下面按“先认清库体、再跑起来、然后备份迁移、最后避坑”的顺序拆,能照着复现的部分我都给到命令和参数。
2. 拆开 wi6.5 的库体:目录布局、核心表与三条血缘关系
2.1 先分清哪些文件能碰、哪些碰了就起不来
wi6.5 这类工作站数据库最容易被误伤的地方,是大家把它当成一个“文件夹里的数据”来处理。实际上它的安装目录里至少混着四类东西:真正的主数据文件、事务日志、系统字典、以及运行期的锁和临时文件。前两类是库的命根子,第三类决定收费项目能不能对上,第四类删了没事但运行中动它会直接把会话锁死。我一般的做法是先把安装目录整个列一遍,按扩展名和修改时间分类,再决定哪些进备份范围。
| 文件类别 | 典型位置 | 能否直接操作 | 风险点 |
|---|---|---|---|
| 主数据文件 | 安装目录下 data 子目录 | 仅停机后可复制 | 运行中复制得到的是不一致镜像 |
| 事务日志 | 安装目录下 log 子目录 | 不可手工删除或截断 | 删日志后库可能直接打不开 |
| 系统字典/系统表 | 安装目录下 system 子目录 | 只读,禁止手改 | 字典损坏会导致收费项目、科室、医生对不上 |
| 配置文件 | 安装目录下的 ini 文件、注册表实例项 | 改前必须备份 | 实例名或端口写错,客户端连不上 |
| 临时与锁文件 | 安装目录下 temp 子目录 | 停机后可清理 | 运行中清理会锁死当前会话 |
确认服务是否在跑,别只靠任务管理器看进程名,XP 上很多老服务是以系统服务方式注册的,进程名和显示名对不上。我习惯先看服务状态,再看端口占用,两步都过了才敢动文件:
REM 查看数据库与应用服务状态,服务名以实际注册名为准 sc query "WI65DB" sc query "WI65APP" REM 看监听端口是否被别的进程占了 netstat -ano | findstr ":7000" REM 看数据目录有没有被进程句柄占用(需自备 handle 工具) handle.exe "D:\WI65\DATA"sc query返回RUNNING说明服务活着,此时任何文件级复制都不可信;netstat那一步是为了排除同机装了两套 wi6.5、端口撞车的情况,这种事故在老门诊机器上并不少见,表现是“服务起来了但客户端连上就断”。handle.exe不是系统自带,没有也能干活,用“停服务后重命名 data 目录”这个土办法验证是否还有占用,重命名成功就说明句柄全释放了。
提示:动手前先把整个安装目录复制一份到移动硬盘,哪怕它有几个 GB。老库的价值不在体积,在于那套字典可能再也配不出来了。
2.2 门诊、收费、医嘱三组核心表怎么读
wi6.5 的表不算多,但字段命名带着明显的年代感,常见的是拼音首字母缩写混英文。真正需要吃透的是三组血缘:患者主索引到就诊流水是一对多,就诊流水到收费明细是一对多,就诊流水到医嘱也是一对多。读数据的时候如果只查单表,很容易得出“这个患者没交钱”的错误结论,因为退费记录常常是负数金额插在明细里,而不是把原记录删掉。
| 主表 | 从表 | 关联键 | 基数 | 备注 |
|---|---|---|---|---|
| patient | visit | patient_id | 1:N | 老库常见一卡多人、一人多卡,需要先去重 |
| visit | charge_detail | visit_id | 1:N | 退费以负金额记录存在,统计时要带符号求和 |
| visit | order_item | visit_id | 1:N | 长期医嘱可能跨就诊,不能只按单次就诊算 |
| drug_dict | order_item | drug_code | 1:N | 字典停用后历史医嘱仍引用,不能物理删除字典行 |
把一次就诊的完整链路串起来,最稳的写法是从患者主索引出发,收费和医嘱都用左连接,因为实际业务里经常出现“开了医嘱还没收费”或者“收费了但医嘱没录”的中间态:
-- 按患者主索引串起就诊、收费、医嘱,左连接保证中间态记录不丢 SELECT p.patient_id, p.name, v.visit_id, v.visit_time, c.item_name, c.qty, c.amount, o.drug_name, o.dosage FROM patient p JOIN visit v ON v.patient_id = p.patient_id LEFT JOIN charge_detail c ON c.visit_id = v.visit_id LEFT JOIN order_item o ON o.visit_id = v.visit_id WHERE p.patient_id = ? ORDER BY v.visit_time DESC;?是患者主索引,用参数占位而不是字符串拼接,一是防注入,二是老驱动对中文参数的转义处理很别扭。LEFT JOIN不是随手写的,收费和医嘱两边任意一侧缺失都属正常业务状态,用内连接会把“只挂号没看病”的记录筛掉,对账时就少人头。ORDER BY visit_time DESC保证返回结果按时间倒序,收费窗口查最近一次就诊时不用再翻页。
脏数据排查是接手老库的第一课,最常见的是孤儿就诊记录——有 visit 没有对应 patient。成因通常是当年手工改库、或者导入时主索引被重排过:
-- 找没有主索引的就诊记录,结果为 0 才说明血缘完整 SELECT v.visit_id, v.patient_id, v.visit_time FROM visit v LEFT JOIN patient p ON p.patient_id = v.patient_id WHERE p.patient_id IS NULL ORDER BY v.visit_time DESC;这条语句我建议在迁移前后各跑一次,结果不一致就说明迁移过程动了主键。另外注意 XP 机器的内存普遍在 512MB 到 2GB 之间,任何不带时间范围的SELECT *都可能把机器拖到假死,巡检类查询务必带上visit_time >= ?这样的边界条件,索引也优先建在visit_time和patient_id上。
3. 在 XP 工作站上把库跑起来:依赖组件、DSN 配置与三条验证 SQL
3.1 运行环境与依赖组件清单
XP 工作站上装 wi6.5,翻车点很少出在数据库本身,多半是环境缺件。安装包通常自带一份 readme,但那份文档写得像说明书,不会告诉你缺了某个运行库时程序是“闪退”而不是“报错”。我整理了一张对照表,装机前逐项过一遍,比装完再猜要省时间。
| 组件 | 装机时该有的状态 | 检查方法 | 缺了会怎样 |
|---|---|---|---|
| 操作系统与补丁 | XP SP3,版本号 5.1.2600 | systeminfo | 安装包直接拒绝启动,或驱动装不上 |
| 数据库服务 | 已注册为系统服务 | services.msc看启动类型 | 客户端报“无法连接实例” |
| 32 位 ODBC 驱动 | 与库版本匹配 | 控制面板→管理工具→数据源 | 配 DSN 时驱动列表里找不到对应项 |
| VC/.NET 运行库 | 按安装包说明装齐 | 注册表 Uninstall 项 | 主程序闪退,事件查看器里有模块加载失败 |
| 打印机与票据模板 | 默认打印机可打测试页 | 打印测试页 | 收费能入库但打不出票,收费员会以为没保存 |
XP 只有 32 位,所以不用担心 64 位驱动装错位置的问题,但要留意驱动版本和库版本必须对得上。有些安装包图省事,把驱动和库打包成同一个版本号,实际驱动是旧的,表现就是 DSN 配好了、测试连接也通,但一查中文就乱码。这种情况换驱动比改配置有效。
REM 确认系统版本与补丁级别 systeminfo | findstr /C:"OS Name" /C:"OS Version" REM 列出已安装的运行库,按关键字过滤 reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s | findstr /I "Runtime"systeminfo在 XP 上跑得慢,等十几秒正常,别以为卡死了。reg query那条会把所有卸载项刷一遍,输出很长,用findstr过滤后只看包含 Runtime 的行即可;如果没有输出,基本可以判定运行库没装全,先去补运行库再谈连库。
3.2 连接配置与三条最小验证 SQL
连接配置的核心只有一句:给服务用的连接要配系统 DSN,不是用户 DSN。因为数据库服务通常以本地系统账户运行,读不到当前登录用户的用户级数据源,这是老工作站上最高频的“配置明明对了却连不上”。配置路径是控制面板→管理工具→数据源(ODBC)→系统 DSN→添加→选对应驱动→填实例名或库文件路径→填只读账号。
账号这块我强烈建议单独建一个只读账号给巡检和报表用,别拿管理员账号到处连。老库的权限模型通常很粗,但只要有独立的只读账号,至少能防住脚本写错导致的误更新。
# -*- coding: utf-8 -*- # XP 上常见的是 Python 2.7 + pyodbc,下面写法两个版本都能跑 import pyodbc conn = pyodbc.connect( "DSN=wi65_local;" # 系统 DSN 名,必须和 ODBC 里配的一致 "UID=reader;" # 只读账号,巡检不要用管理员 "PWD=******;" "CHARSET=GBK;" # 字符集与库一致,中文系统通常是 GBK "TIMEOUT=10;", # 老机器别设太长,卡住要能快速失败 timeout=10 ) cur = conn.cursor() cur.execute("SELECT COUNT(*) FROM patient") print(cur.fetchone()[0])CHARSET是最容易写错的一项,写成 UTF8 而库是 GBK,查出来的姓名全是问号或乱码,很多人第一反应是库坏了,其实是连接参数不匹配。TIMEOUT设 10 秒是有意的,XP 机器磁盘慢,查询卡住时如果超时设得太长,收费员会一直等,不如快速失败让程序给出明确提示。
连上之后别急着跑业务查询,先用三条语句确认库是“活的、全的、新的”:
-- 1. 通不通:能返回 1 就说明连接链路没问题 SELECT 1; -- 2. 全不全:字典表有效记录数,为 0 说明恢复时漏了 system 目录 SELECT COUNT(*) FROM drug_dict WHERE valid_flag = 1; -- 3. 新不新:最新一条就诊时间,明显落后说明采集或同步断了 SELECT MAX(visit_time) FROM visit;三条结果对应三种故障:第一条失败是驱动、DSN、账号层面的问题;第二条为 0 或者远小于预期,说明恢复时只拷了 data 没拷 system;第三条如果最新时间停在几天前,业务还在跑就是写入没落库,业务停了就是同步链路断了。这三条我每次接老库都先跑一遍,比看任何日志都快。
4. 备份与迁移的四步法:从冷备时间窗到回滚预案
4.1 冷备与热备:什么时间点停业务最划算
老库的备份只有两种打法:停库整目录拷贝,或者在线导出。前者一致性最高,代价是要停业务;后者不停机,但依赖事务日志完整,而 wi6.5 这类工作站库很多根本没开归档,热备经常是“看起来成功了,恢复时发现少了最后半小时”。门诊场景下我一般选午休或者晚间结账后做冷备,停 10 到 30 分钟,收费窗口能接受,数据也最干净。
| 维度 | 冷备(停库整目录拷) | 热备(在线导出/日志) |
|---|---|---|
| 业务影响 | 需停 10–30 分钟 | 不停机 |
| 一致性 | 最高,目录即快照 | 取决于日志是否完整 |
| 恢复速度 | 直接覆盖,最快 | 需重放日志,耗时不可控 |
| 适用场景 | 每日夜间、版本升级前、迁移前 | 白天增量、临时取数 |
| 主要风险 | 停库窗口没和业务谈好 | 老库常没开归档,容易假成功 |
判断热备是不是真的可用,有个笨但有效的办法:在测试环境恢复一次,对比charge_detail的金额合计和行数。对不上就说明日志不全,老老实实回冷备。这个验证别省,血泪经验是热备脚本跑了一整年、真出事时才发现恢复不出来。
4.2 导出、校验、导入、回滚的完整脚本
冷备脚本要干四件事:停服务、整目录复制、核对文件数与体积、起服务。XP 上没有可靠的 md5 工具(certutil 的 hashfile 参数是后加的功能),所以用文件数量和总字节数做粗校验,够用且不依赖外部程序。
@echo off setlocal set SRC=D:\WI65\DATA set BAK=E:\BAK\WI65_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2% REM 1) 先停应用再停库,顺序反了会导致应用反复重连 net stop "WI65APP" net stop "WI65DB" || goto :fail REM 2) 整目录复制,保留只读属性和空目录 xcopy "%SRC%" "%BAK%\" /E /I /H /K /Y || goto :fail REM 3) 核对源目录与备份目录的文件数(中文系统 dir 结尾是“个文件”) for /f "tokens=1" %%a in ('dir /s /-c "%SRC%" ^| find "个文件"') do set N1=%%a for /f "tokens=1" %%a in ('dir /s /-c "%BAK%" ^| find "个文件"') do set N2=%%a echo 源文件数=%N1% 备份文件数=%N2% if not "%N1%"=="%N2%" goto :fail net start "WI65DB" net start "WI65APP" echo backup ok: %BAK% exit /b 0 :fail echo 备份异常,服务未启动,请人工检查 exit /b 1net stop的服务名要换成实际注册名,脚本里写的是占位。/E /I /H /K /Y这几个参数分别对应“含空目录、目标当目录、含隐藏文件、保留只读属性、不询问覆盖”,少一个都可能出现恢复后少文件。文件数比对那两行依赖中文系统的dir输出,英文系统要把“个文件”换成 “File(s)”,这是脚本唯一不跨语言的地方。
迁移到新机器是另一套动作,顺序不能乱:先在新机器装好同版本库并把服务跑起来(空库),再改配置里的实例名和路径指向新目录,然后导数据,最后对账。对账 SQL 就三条,行数和金额必须和旧库完全一致:
-- 迁移前后各跑一次,三个数必须一模一样 SELECT COUNT(*) AS 就诊数 FROM visit; SELECT SUM(amount) AS 收费总额 FROM charge_detail WHERE pay_flag = 1; SELECT COUNT(*) AS 药品数 FROM drug_dict;金额那一条最容易出问题,因为退费是负金额,SUM天然带符号,比按条数对更敏感。如果总额差了但行数一致,八成是某几条记录的金额字段在导出时被转成了字符串或者被四舍五入。回滚预案也要提前写死:旧目录不删、只改配置指回去,服务重启即恢复,千万别在新环境验证通过之前格式化旧盘。
5. 排查与避坑:XP 工作站上五类高频翻车现场
5.1 连接与启动类:DSN、实例名与端口
现象:ODBC 里测试连接成功,业务程序却报“找不到数据源”。原因多半是配了用户 DSN,而服务以本地系统账户运行,读的是系统 DSN。解决方法是删掉用户 DSN,在系统 DSN 里重建一份同名数据源,重建后重启服务再试。这个坑我见过至少三次,每次都是配置的人当前登录着管理员账户,自己测通了就以为完事。
现象:库服务能起,客户端连上几秒就断,反复重连。原因通常是同机装了两套 wi6.5(一套测试一套生产),实例名或端口撞车,或者 XP 本身的并发连接限制被打满。排查时先netstat -ano看端口占用,再sc query看是不是两个服务都在跑。解决方式是给测试实例改独立端口和独立数据目录,别图省事共用一套。
现象:服务启动时报“无法初始化实例”,事件查看器里没有有用信息。原因往往是上次非正常关机留下的锁文件或日志尾部损坏。处理顺序是先把 temp 目录清空,再尝试启动;仍然失败就检查 log 目录里最后一个日志文件的大小和时间,必要时用安装包自带的修复工具做一致性检查,不要上来就重建库。
5.2 数据与备份类:漏拷、假成功与字符集
现象:恢复后能登录,但收费项目全是空,医生选不了。原因是备份时只拷了 data 目录,漏了 system 字典目录。这类事故的隐蔽性在于库能打开、患者也能查,直到收费窗口开单才发现。解决办法是把 system 目录纳入备份范围,并在恢复后用第 3 章那条字典计数 SQL 做一次校验,数量对不上就不算恢复完成。
现象:热备脚本每晚都成功,真恢复时少了当天最后一段数据。原因是库没开归档,在线导出拿到的是某个时间点的不完整快照。判断办法是恢复后比对最大visit_time与备份开始时间,差得远就说明热备不可信。结论很直接:这种库就老老实实做冷备,别在热备上浪费时间。
现象:迁到新机器后姓名和药品名全是乱码。原因是新环境的区域设置或连接字符集与旧库不一致。老库多半是 GBK,新机器如果默认装成了别的代码页,读出来就是问号。解决时先确认旧库字符集,再让 DSN 的CHARSET和系统区域设置都对齐,不要试图在应用层做转码,转两次一定出错。
6. 进阶巡检:只读账号加定时脚本,盯住业务量异常
老库最怕的不是崩,是“悄悄不写入了还没人发现”。我一般会在客户机上挂一个只读巡检脚本,每天固定时间跑一次,把业务量、脏数据、字典状态三个指标落进日志,异常就留痕。查询本身用一条UNION ALL拼完,减少连接往返:
-- 每日巡检三指标,日期参数由脚本传入 SELECT '当日就诊量' AS 指标, COUNT(*) AS 值 FROM visit WHERE visit_time >= ? UNION ALL SELECT '孤儿就诊记录', COUNT(*) FROM visit v LEFT JOIN patient p ON p.patient_id = v.patient_id WHERE p.patient_id IS NULL UNION ALL SELECT '停用药品数', COUNT(*) FROM drug_dict WHERE valid_flag = 0;第一个参数是当天零点,用参数占位而不是拼字符串,避免日期格式随区域设置变化。第二个指标只要大于 0 就说明血缘断了,需要人工核对,不能自动修。第三个指标是观察字典维护是否正常,突然暴涨往往意味着有人在批量停用药品但没走流程。
# -*- coding: utf-8 -*- import datetime, logging, pyodbc logging.basicConfig(filename=r"D:\WI65\check\daily.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") SQL = """SELECT '当日就诊量', COUNT(*) FROM visit WHERE visit_time >= ? UNION ALL SELECT '孤儿就诊记录', COUNT(*) FROM visit v LEFT JOIN patient p ON p.patient_id = v.patient_id WHERE p.patient_id IS NULL UNION ALL SELECT '停用药品数', COUNT(*) FROM drug_dict WHERE valid_flag = 0""" conn = pyodbc.connect("DSN=wi65_local;UID=reader;PWD=******;CHARSET=GBK;", timeout=15) cur = conn.cursor() today = datetime.date.today().strftime("%Y-%m-%d 00:00:00") cur.execute(SQL, today) for name, value in cur.fetchall(): logging.info("%s=%s", name, value) if name == "孤儿就诊记录" and value > 0: logging.warning("血缘异常,需要人工核对")只读账号是这套东西能长期跑的前提,权限给到SELECT就够,别为了省事用管理员。日志按天追加、不自动清理,跑满一个月翻一次,能看出业务量的周节律;如果某天就诊量突然掉到平时的三分之一,多半是采集链路断了而不是门诊真没人。指标阈值不要写死在脚本里,先观察两周再定,老库的业务波动比想象中大。
最后说个教训:我早期图快,直接在业务高峰跑全表统计,结果把 XP 机器的内存吃满,收费窗口集体卡住十分钟。从那以后我每次接老库,都先把只读账号、巡检脚本和停库窗口这三件事确认一遍,再谈其他优化——顺序错了,技术再对也是事故。希望帮到你。
本文还有配套的精品资源,点击获取