MongoDB电子病历建模核心是合理运用嵌套、数组与动态字段,按临床实体拆分集合,对敏感字段启用CSFLE加密,生命体征用time-series集合优化,避免滥用$lookup。
建模电子病历,核心不是“怎么塞进文档”,而是
用好嵌套、数组和动态字段的组合逻辑,同时守住临床语义边界
。硬套 JSON 自由度反而导致后期查询崩、权限失控、审计失效。
嵌套结构要分层,别一股脑全塞进
常见错误是把整份病历当一个大 JSON 字段存,比如:
。表面省事,实际埋雷:
无法对
或
建高效索引
更新某次就诊记录时,得读-改-写整个文档,放大写放大锁
审计日志只能记“
表更新”,看不到具体改了哪条血压值
推荐做法是按临床实体拆表(collection),但保留嵌套合理性:
这样既能对
、
单独建索引,又避免跨文档关联——
和
用
关联即可。
联合查询前先确认是否真需要实时 JOIN
电子病历里“患者全病程视图”常被当作刚需,但
在高并发下极易拖慢响应。某三甲医院实测:单次
关联 3 张表(
+
+
)平均耗时 2.4 秒,超临床容忍阈值。
更务实的策略是:
医生端高频操作(如调阅本次就诊)直接查
,
和
已内嵌,毫秒级返回
质控/科研等低频场景才走
,且限定时间范围(如最近 30 天)+ 加
对真正需要宽表的报表,用 Change Stream + Kafka 同步到 OLAP 库(如 Doris),不压 MongoDB
敏感字段不能靠应用层“自觉加密”,必须用
病历里的
、
、
等字段,仅靠 C# 或 Java 层加密远远不够——DBA 可直连数据库导出裸数据,备份文件也明文可读。
MongoDB For Windows v3.5.4
MongoDB For Windows v3.5.4
下载
支持客户端字段级加密(CSFLE),密钥由 KMS(如 HashiCorp Vault)托管,数据库全程只存密文:
注意两个坑:
确定性加密(
)支持等值查询,但会泄露重复值分布;随机加密(
)更安全,但只能查不能
启用 CSFLE 后,
默认不导出密钥,需显式加
,否则恢复失败
别忽略
集合对生命体征类数据的优化价值
监护仪、可穿戴设备产生的连续血压、心率、血氧数据,每秒多条,若全塞进普通 collection,索引膨胀快、TTL 删除不准、聚合慢。
的 time-series 集合专治这类场景:
实测同样 1000 万条心率数据,time-series 集合体积比普通集合小 62%,
聚合提速 3.8 倍——但注意它不支持二级索引、事务、变更流,纯做时序分析用。
真正难的不是建模本身,是临床逻辑和数据库能力的咬合点:什么时候该拆 collection,什么时候该留嵌套,哪些字段必须加密,哪些聚合可以离线。这些判断没法靠文档自由度自动解决,得拿真实业务流去试错。
MongoDBreport_data{"patient":{...},"visits":[{...}], "labs":[{...}]}visits.datelabs.result_valueemr_records// 患者主表(稳定字段强约束)
{
"_id": ObjectId("..."),
"patient_id": "P2024001",
"name": "张三",
"id_card": "11010119900307211X", // 加密存储
"gender": "M",
"birth_date": ISODate("1990-03-07")
}
// 就诊记录表(每次门诊/住院一条,含嵌套 vitals、diagnosis)
{
"_id": ObjectId("..."),
"visit_id": "V20240601-001",
"patient_id": "P2024001",
"date": ISODate("2024-06-01T09:15:00Z"),
"vitals": {
"blood_pressure": "138/86",
"heart_rate": 78,
"temperature": 36.5
},
"diagnosis": ["高血压2级", "2型糖尿病"],
"notes": [
{ "role": "doctor", "content": "建议加用ARB类降压药" },
{ "role": "nurse", "content": "已测血糖,空腹6.2mmol/L" }
]
}
datevitals.blood_pressurevisitslabsvisit_id$lookup$lookup$lookuppatientsvisitslabsvisitsvitalsdiagnosis$lookupallowDiskUse: trueclient-side field level encryptionid_cardfamily_historygenetic_infoMongoDB 4.2+const autoEncryptionOpts = {
keyVaultNamespace: "encryption.__keyVault",
kmsProviders: { "local": { "key": new Buffer.from(key, "base64") } },
schemaMap: {
"emr.visits": {
"properties": {
"id_card": { "encrypt": { "keyId": [keyId], "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" } },
"family_history": { "encrypt": { "keyId": [keyId], "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" } }
}
}
}
};DeterministicRandomWHEREmongodump--master-key-filetime-seriesMongoDB 5.0+db.createCollection("vitals_timeseries", {
"timeseries": {
"timeField": "timestamp",
"metaField": "patient_id",
"granularity": "minutes"
}
});
// 写入自动按时间窗口压缩,查询天然支持 window、$dateTrunc
db.vitals_timeseries.aggregate([
{ $match: { patient_id: "P2024001", timestamp: { $gt: ISODate("2024-06-01") } } },
{ $group: { _id: { $dateTrunc: { date: "$timestamp", unit: "hour" } }, avg_bp: { $avg: "$systolic" } } }
]);
$avg