不能把所有历史版本塞进一个文档的数组里,应采用“主文档 + 版本集合 + schemaVersion 字段”三件套:emr_current存最新快照,emr_versions独立存每版记录并建复合索引{emr_id:1, timestamp:-1},schemaVersion用字符串如" v2.0 "显式标识兼容性,关键审计字段需提前冗余。

直接结论:别用单文档存全量病历版本,用“主文档 + 版本集合 + schemaVersion 字段”三件套,否则后期查某次修改、做合规审计、回滚特定字段时会卡死。
为什么不能把所有历史版本塞进一个文档的数组里
常见错误是这样设计:emr_records 集合里一个文档包含 versions: [{_id, timestamp, content}, ...]。表面看结构清晰,实际踩坑密集:
- 每次新增版本都要
$push到数组,触发整个文档重写,锁粒度大,高并发下写冲突频发 - 无法对
versions.timestamp单独建高效索引(MongoDB 不支持对数组内嵌文档的非前缀字段建唯一索引) - 审计日志只能记录“
emr_records更新”,看不到具体改了哪条医嘱或哪个生命体征值 - 某次误操作要回滚第 3 版,得先读出全部版本、过滤、再构造新数组写入——应用层逻辑爆炸
推荐结构:主文档只存当前态,历史版本独立集合
拆成两个集合协作,兼顾查询效率与版本控制能力:
-
emr_current:只存患者最新一次完整病历快照,字段含patient_id、last_version_id、updated_at,用于高频读取 -
emr_versions:每条记录是一个独立文档,含_id(ObjectId)、emr_id(关联主文档)、schemaVersion(如"v2.1")、timestamp、diff(JSON Patch 格式,只存变更字段)或full_snapshot(全量,按需选) - 在
emr_versions上建复合索引:{emr_id: 1, timestamp: -1},支撑“查某患者所有版本”和“查最新版”两种高频场景
schemaVersion 字段怎么用才不翻车
schemaVersion 不是装饰,是版本兼容性的执行开关:
- 必须作为显式字段写入每个
emr_versions文档,不能靠时间戳推断 - 应用读取时先取
schemaVersion,再加载对应解析逻辑(比如v1.0用blood_pressure字段,v2.0改为vitals.blood_pressure.systolic/diastolic) - 迁移新 schema 时,新写入的版本用新
schemaVersion,旧版本不动——避免全量 reindex 或停机 - 禁止用数字型 version(如
2),统一用字符串"v2.0",防止语义混淆和排序错乱
增量 diff 还是全量 snapshot?看你的审计要求
选哪种取决于你是否需要精确还原任意时刻的“完整状态”:
- 选
diff:节省空间、写入快,但还原某版本需从基线开始逐条 apply patch,不可逆操作(如删除字段)难追溯 - 选
full_snapshot:占空间大,但审计时可直接find({emr_id: "...", timestamp: {$lt: ISODate("...")}}).sort({timestamp: -1}).limit(1)拿到当时完整快照,符合等保三级对“操作留痕可验证”的硬性要求 - 折中方案:只对关键字段(如诊断、处方、手术记录)存
full_snapshot,其余用diff,用type字段区分
真正麻烦的不是存版本,而是当监管突然要求“导出患者张三自2024年1月起所有血压值修改记录并附操作人”时,你能不能在 5 秒内跑出带用户 ID、IP、时间戳、前后值的明细表——这决定了你该在 emr_versions 里提前冗余哪些字段,而不是等出事了再加索引。


















