2. 临床数据模型与查询负载分析

临床数据SQL性能优化不能脱离数据模型空谈索引与改写。电子病历、检验、影像、随访四类数据在结构、体量、增长方式和访问模式上差异极大,只有先把模型特征与查询负载对应起来,才能判断一条慢SQL究竟卡在数据量、连接方式还是访问路径上。本节围绕这四类模型展开,并给出负载画像与性能基线的建立方法。

四类临床数据模型的结构特征

电子病历以患者为主实体,围绕就诊(encounter)组织病程记录、医嘱、诊断、护理记录等。典型结构是患者表—就诊表—明细表的三层关系,明细表通过 encounter_id 关联,单次住院可能产生数百到数千条明细。这类模型的特点是连接层级深、时间字段多、文本字段宽,查询常带患者维度与时间范围双重过滤。

检验数据是典型的“主记录+结果明细”结构:一张申请单对应多条检验项目结果,结果表往往包含数值结果、单位、参考区间、异常标志。它的体量增长最快,单家三甲医院检验结果表年增可达数千万到上亿行,且结果值列区分度低、时间分布集中,索引设计需要特别关注组合顺序。

影像数据本身以文件为主,数据库侧通常只存检查登记、报告文本和关键影像索引。它的特点是单行记录不大但报告文本较长,查询多为按患者、检查时间、检查类型检索,且常与病理、检验结果做时间对齐。

随访数据是纵向稀疏结构:同一患者在不同随访时点产生多条记录,字段随随访表单变化,常见做法是宽表或键值对存储。它的查询模式偏向“取某患者最近一次随访”或“按随访时点做趋势对比”,对时间排序和分组取最新的性能敏感。

临床四类数据模型与典型查询负载对应关系 电子病历 EMR 患者 patient 就诊 encounter 病程/医嘱/诊断明细 层级深 · 时间字段多 · 文本宽 检验 LAB 申请单 lab_order 结果明细 lab_result 数值+单位+参考区间+异常标志 体量最大 · 年增千万~亿行 影像 IMG 检查登记 exam 报告文本 report 文件在PACS · 库内仅索引 行不大 · 报告文本长 随访 FOLLOW 患者随访主表 随访时点明细 纵向稀疏 · 字段随表单变化 取最近一次 · 趋势对比 典型查询负载画像(按访问模式归类) 点查:患者ID+时间 高频 · 低延迟要求 范围扫描:时间窗 中频 · 结果集大 聚合:分组统计 低频 · CPU与IO重 多表连接:跨域对齐 低频 · 连接顺序敏感 性能基线四要素 ① 数据量基线 行数/表大小/日增量 ② 响应时间基线 P50/P95/超时率 ③ 资源基线 CPU/IO/内存/连接数 ④ 执行计划基线 扫描方式/连接算法

典型查询模式与访问路径

把负载归为四类后,优化方向就清晰了。点查类如“按患者ID取最近一次检验结果”,理想路径是走 (patient_id, result_time) 组合索引,先定位患者再按时间倒序取第一行,避免全表扫描。范围扫描类如“取某科室某月全部检验结果”,需要评估时间列的选择性,若单月数据占比过高,索引可能不如分区裁剪有效。

聚合类如“统计各病区异常检验项目数”,瓶颈常在分组前的过滤与连接。应先用时间与病区条件缩小结果集,再做分组,必要时用覆盖索引避免回表。多表连接类如“将影像报告与同期检验结果按患者和时间对齐”,连接顺序至关重要:应先过滤出小结果集,再与大表连接,否则中间结果会膨胀数倍。

随访数据的“取最近一次”是高频难点。常见写法是用子查询取 max(follow_time) 再关联,若随访表很大且无合适索引,会反复扫描。更稳的做法是建立 (patient_id, follow_time DESC) 索引,或使用窗口函数一次排序取行号,减少重复访问。

负载画像的采集方法

负载画像不是凭经验猜测,而是从真实执行记录中提取。可从数据库的慢查询日志、性能视图(如 MySQL 的 performance_schema、PostgreSQL 的 pg_stat_statements)中按“执行次数×平均耗时”排序,找出既频繁又慢的语句。对每条语句记录:涉及表、过滤字段、返回行数、执行计划中的扫描方式与连接算法。

采集时要注意区分业务时段。门诊高峰与夜间批处理的负载特征完全不同,混在一起会掩盖真实瓶颈。建议按小时或按业务场景分组统计,形成“时段—语句—资源”的对应关系。

性能基线的建立与使用

基线是后续优化的参照系。建立基线需要固定测试条件:相同数据量、相同并发、相同硬件与参数配置。对每条关键查询记录 P50、P95 响应时间,以及执行计划中的关键算子。基线一旦建立,任何索引调整或SQL改写都应与之对比,判断是真实提升还是波动。

下面是一份可直接使用的性能基线记录表示例,字段按临床查询场景设计:

查询编号业务场景涉及表过滤字段P50(ms)P95(ms)扫描方式返回行数
Q-EMR-01取患者住院病程encounter, progress_notepatient_id, admit_time45180索引范围扫描120
Q-LAB-02取最近一次检验结果lab_resultpatient_id, result_time1260组合索引倒序1
Q-LAB-05某月异常项目统计lab_resultdept, result_time, flag8202600全表扫描+分组35000
Q-IMG-03影像报告与检验对齐exam, report, lab_resultpatient_id, exam_time3101100嵌套循环连接80
Q-FOL-01取最近一次随访follow_uppatient_id95420子查询+回表1
基线表中的 P95 比 P50 更能反映用户体验。临床场景下,检验结果查询若 P95 超过 1 秒,医生端就会出现明显等待感,应优先处理。

从画像到优化决策

有了模型特征、负载分类和基线数据,优化决策就有了依据。若某条查询执行次数高但单次很快,重点在减少调用次数或加缓存;若执行次数低但单次极慢,重点在改写SQL或调整索引;若执行计划显示全表扫描且过滤后行数很少,说明缺少合适索引;若连接算法选择嵌套循环但驱动表行数很大,说明统计信息或连接顺序需要调整。

需要强调的是,基线不是一次性的。数据量增长、业务规则变化、索引增删都会使基线漂移。建议在每次重大变更后重新采集关键查询的基线,保持参照系有效。临床数据模型与查询负载分析的价值,正在于把“感觉慢”变成“可度量、可定位、可验证”的工程问题。