从 10 秒到 1 秒:一次标准差判读的批量查询与索引优化

发布时间:2026/10/29 11:05:05

从 10 秒到 1 秒:一次标准差判读的批量查询与索引优化 本文复盘一次标准差判读功能的性能优化合并查询、建立内存索引、减少重复遍历并修复历史实现中的结果覆盖等问题。文中结论以源码和业务观察为依据新旧结果是否一致尚未经过同批数据验证。1. 背景与问题现象这是实验室质控平台的能力验证判读功能。组织方把同一批质控样本分发给各实验室各实验室上报检测值平台按样本类别汇总所有机构的值计算均值M和样本标准差S再用|x − M|与1S / 1.5S / 2S / 3S比较给出单样本评价最后合成机构综合评价。判读有三条入口详情页、成绩计算和 Excel 导出三条路径使用同一套判读逻辑。数据来自filling_sample、qa_task_details、qa_task_sample三张表qa_filling_tasks.status用于过滤有效填报。优化前详情页业务侧需要等待10 秒以上改造后降到约 1 秒。这个数字来自实际使用中的观察并非正式压测没有记录并发量、机器环境和 P95因此不能把它表述为所有场景下都稳定提升十倍。2. 性能瓶颈定位旧实现先查任务明细再查检测值。两条查询都在循环外所以问题不是 N1真正的浪费发生在 Java 端的数据处理。// detailsBySample、valuesBySample 是两条查询结果按样本类别分组后的 MapfillingTaskMap.forEach((labId,lab)-{// F 家机构qaTaskSampleMap.forEach((sampleId,sample)-{// S 类样本detailsBySample.get(sampleId).forEach(detail-{// 约 F 条明细// 旧代码此处用明细 ID 查按样本类别 ID 建的索引valuesBySample.get(detail.getId()).stream().collect(Collectors.groupingBy(CodeValueBySample::getId));});});});问题集中在三处外层已经在遍历机构内层却再次取出“该类样本下所有机构的明细”在索引能命中的前提下同一批记录会被重复处理约F次最内层对已经查到的记录重新groupingBy、toMap属于重复分组和重复查找结果集合也在明细循环内创建和添加带来了结果覆盖、重复添加等问题见第 5 节。旧实现已经在循环外计算均值和标准差这部分不是本次优化新增的收益。3. SQL 查询优化旧实现先查任务明细select *带det.del_flag 0没有状态过滤再查检测值关联qa_task_details按status in (2,4,5)过滤。两条 SQL 在qa_task_details上重复取数。合并后的查询只取必要字段并保留状态过滤selectdet.filling_task_id,det.qa_task_sample_id,sample.valuefromfilling_sample samplejoinqa_task_details detondet.idsample.qa_task_details_idwheresample.template_instance_item_info_id#{dataSourceId}anddet.task_id#{taskId}andexists(select1fromqa_filling_tasks fwheref.iddet.filling_task_idandf.statusin(2,4,5))这组判读数据查询中减少了一次数据库访问也少传输了一份任务明细结果集行数取决于实际明细数量记为D少做了一次 Java 侧分组。示例省略了实际 SQL 中的det.id、qs.name、det.sample_code等字段数据库端收益仍取决于执行计划本文未做 SQL 日志对比。有两点结果范围差异必须保留不能宣称新旧 SQL 完全等价旧明细查询带det.del_flag 0新查询没有如果存在逻辑删除的任务明细结果集范围会不同。旧明细查询没有状态过滤新查询只保留status in (2,4,5)。其中2、4在实体注释中分别对应“已填报”“已评分”5没有注释说明本文不推断其业务含义。4. 内存索引与循环优化拿到N条检测记录后先按两个访问路径建立索引。这里的all是批量查询返回的记录sampleIndex是“样本类别 id - 样本对象”的有序 Map。下面的代码为突出索引与循环结构做了简化省略了结果组装、异常处理和业务分支不是可直接替换生产实现的完整代码。// 样本类别 - 该类样本下所有机构的检测值用于统计MapLong,ListCodeValueBySamplebySampleall.stream().collect(Collectors.groupingBy(CodeValueBySample::getQaTaskSampleId));// 机构 - 该机构的检测值用于逐家判读// key 是 qa_filling_tasks.id与 fillingTaskMap 的 key 同维度MapLong,ListCodeValueBySamplebyFillingTaskall.stream().collect(Collectors.groupingBy(CodeValueBySample::getFillingTaskId));均值和标准差是样本级不变量在机构循环外只算一次MapLong,BigDecimalmeanMapnewHashMap();MapLong,BigDecimalsdMapnewHashMap();sampleIndex.forEach((sampleId,sample)-{ListCodeValueBySamplesampleValuesbySample.get(sampleId);if(sampleValuesnull||sampleValues.isEmpty()){thrownewIllegalStateException(缺少检测记录需按业务规则处理);}ListBigDecimalvaluessampleValues.stream().map(CodeValueBySample::getValue).collect(Collectors.toList());meanMap.put(sampleId,EquipStdDevUtils.mean(values,10));sdMap.put(sampleId,EquipStdDevUtils.sampleStandardDeviation(values,10));});判读时按机构循环在机构级建立一次“样本类别 - 记录”的索引再按样本直接查找ListSampleStdDevQaFillingTaskresultsnewArrayList();// fillingTaskMap: qa_filling_tasks.id - SampleStdDevQaFillingTaskbyFillingTask.forEach((labId,labValues)-{// labId 是 getFillingTaskId() 返回的 qa_filling_tasks.idSampleStdDevQaFillingTasklabfillingTaskMap.get(labId);if(labnull){thrownewIllegalStateException(缺少机构任务记录需按业务规则处理);}MapString,ObjectsampleMapnewLinkedHashMap();MapLong,CodeValueBySamplevalueIndexlabValues.stream().collect(Collectors.toMap(CodeValueBySample::getQaTaskSampleId,e-e,(oldValue,newValue)-oldValue));sampleIndex.forEach((sampleId,sample)-{CodeValueBySamplerecordvalueIndex.get(sampleId);BigDecimalmeanmeanMap.get(sampleId);BigDecimalsdsdMap.get(sampleId);// record、value、mean、sd 都可能缺失处理规则待业务确认if(recordnull||record.getValue()null||meannull||sdnull){thrownewIllegalStateException(缺少样本记录或统计量需按业务规则处理);}BigDecimalsampleValuerecord.getValue();BigDecimalabsValuesampleValue.subtract(mean.setScale(1,RoundingMode.HALF_UP)).abs();// 用 absValue 与 1S / 1.5S / 2S / 3S 比较 ...sampleMap.put(sample.getName(),sampleValue);// 组装展示字段简化});lab.setSample(sampleMap);// 每家机构只组装一次// 计算综合评价并回填 lab.setResult(...)此处省略results.add(lab);});说明本节示例中的IllegalStateException只是为避免空指针而写的示意占位不代表生产实现缺失数据是报错、跳过还是按其他规则处理仍需业务确认。核心变化只有两点内层只处理当前机构自己的V条记录重复的stream groupingBy变成一次索引构建 O(1)查找。当前仓库仍把valueIndex建在样本循环内上面是整理后的前置写法属于等价小改动每机构少建S - 1次索引V很小时收益有限不计入已上线的优化。toMap的合并函数保留第一条前提是同一机构同一类别至多一条有效记录若有复测或多指标共用字段后续记录会被静默丢弃需先明确聚合规则。5. 优化效果与正确性检查结果是业务侧观察到的 10 秒以上降到约 1 秒查询合并、索引与循环调整、缺陷修复这三类改动对耗时的贡献无法拆分不能把收益全部归因于某一种优化。复杂度变化设F为机构数S为样本类别数V为每机构每类样本的检测记录数N为批量查询返回的记录总数。数据均衡时N ≈ F × S × V。版本记录处理量说明旧实现约F × N每家机构都把全量数据再走一遍新实现约N查询、分组、统计、索引和查找各一遍旧实现F × N的前提是每机构每类一条明细、索引能命中、每条明细V条记录。旧代码索引键不一致可能提前失败所以这是结构量级不是实测。新实现为O(N)的前提是N ≈ F × S × VV ≥ 1、每家机构都要遍历S个样本类别这样F × S的查找项会被N吸收若V 0或数据严重不平衡应按实际记录数和实际遍历次数分别估算。索引与记录同阶空间复杂度O(N)。正确性检查与本次重构直接相关的源码问题有三类样本结果 Map 在明细循环内创建同一机构的结果会被后续明细覆盖results.add(...)位于明细循环内同一机构对象可能被重复添加旧代码用明细 ID 查询按样本类别 ID 建立的索引两个 ID 不属于同一维度可能取不到数据。新版按机构循环组装和添加各一次。但这些只是源码确认不是生产故障统计新旧结果尚未同批对比仓库也没有自动化测试。待确认风险缺失记录含状态3机构、重复记录以及单样本判定用 1 位小数均值、综合评价用 1 位小数差值带来的阈值边界差异。需业务确认本文不设默认行为。6. 总结与实践经验这次优化没有复杂技巧主要是三件事合并查询先确认查询是否真的在循环里。旧实现的两条查询都在循环外问题不是 N1而是查询结果存在重叠、后续又重复处理。按访问路径建索引按样本统计就建“样本 - 记录”按机构判读就建“机构 - 记录”把重复遍历换成一次分组和直接查找。不变量前置均值、标准差和阈值倍数与机构无关放在循环外只算一次。
延伸阅读

更多相关文章

2026/10/29 11:00:02

【2026】全国大学日语四六级考试官方样题、词汇大纲资料PDF版

全国大学日语四六级考试概述全国大学日语四、六级考试由教育部考试中心主办,是面向非日语专业大学生的全国性日语水平测试。小编整理了全国大学日语四六级考试官方样题、词汇大纲资料PDF版。考试内容与题型四级考试涵盖听力理解、文字与词汇、语法、阅读理解和写作五…

2026/10/29 11:00:02

群晖6.2.3部署akiralereal/iptv

1. 创建目录与 Docker Compose 文件 cd /volume1/docker mkdir akiralereal-iptv cd akiralereal-iptv创建 docker-compose.yml 文件: vi docker-compose.ymlservices:iptv:image: akiralereal/iptv:latestcontainer_name: iptv# init: true # DSM 6.2 可…

2026/10/29 12:25:29

马尾辫技术拆解:发根倒梳、固定与碎发处理全流程

1. 马尾辫这个看似简单的事,其实门道不少ponytail,马尾辫,可能是全世界出现频率最高的发型,没有之一。从幼儿园小姑娘到通勤上班族,从健身房到红毯,马尾辫几乎覆盖了所有年龄层和场景。但恰恰因为它太常见&…

2026/10/29 12:25:29

Material UI滑动选择器样式深度定制指南

1. 项目背景与需求分析 最近在开发一个数据可视化后台时,发现系统原生的滑动选择器(Slider)组件样式过于单调,与整体UI设计风格不协调。经过调研发现,Material UI提供的Slider组件虽然功能完善,但默认样式在以下场景存在不足&…

2026/10/29 12:25:29

大模型:AI时代的操作系统与开发范式变革

1. 大模型:AI时代的操作系统革命 去年第一次用ChatGPT写代码时,我就被它的"思考能力"震惊了。它不仅能理解模糊的需求描述,还能自动补全整个函数实现——这完全颠覆了传统编程工具的使用体验。这种能力的背后,正是大模型…

2026/10/29 12:25:29

强化学习在出行服务调度中的动态优化实践

1. 项目概述:当出行服务遇上强化学习 去年夏天,我在为某出行平台做效率优化咨询时,遇到一个典型难题:早晚高峰时段,平台既需要保障司机收入,又要控制乘客等待时间,还要应对突发天气变化。传统静…

2026/10/29 12:25:29

ponytail 插件实战:从安装到自动化工作流搭建

ponytaill 插件实战:从安装到自动化工作流搭建 说实话,第一次听说 ponytail 这个插件工具时,我以为是哪个开源作者在取名上偷了懒——马尾巴?皮筋?直到真正用了几天,才发现这东西解决的是我工作中最头疼的重…

2026/10/29 12:20:29

ico小头像设置全攻略:从PNG到多尺寸ICO的转换、应用与避坑指南

简介:这份资源是一份关于ico小头像(favicon)设置的入门教程文档,主要面向需要为网站添加浏览器图标标识的站长、前端开发者和网站运营人员。文档从favicon的起源与作用讲起,说明其在品牌展示和用户体验中的价值&#x…

2026/10/28 14:42:12

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/28 15:28:56

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/28 3:32:29

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/29 0:03:42

学生选课系统DFD图绘制指南:边界划分与分层校验实战

简介:文档以顶层与第一层DFD图为核心,完整刻画学生选课系统的逻辑模型,面向软件工程课程设计、系统分析学习及毕设前期建模人群。顶层图界定系统边界,外部实体包括管理员、教师和学生三类角色;第一层图进一步细化用户登…

2026/10/29 0:03:42

JIRA敏捷落地:Scrum角色权限、Sprint与燃尽图避坑指南

简介:围绕“基于JIRA的敏捷开发项目管理”这一主题,面向产品负责人、Scrum Master及研发测试成员,梳理Scrum角色分工、迭代流程与JIRA工具配置的对应关系,适合初、中级团队导入敏捷管理时参考。压缩包共1个docx文件,大…

2026/10/29 0:03:42

SEMI S8人机工程验收实战:从条款拆解到现场实测的完整指南

简介:面向半导体设备设计、制造与运维工程师及安全合规人员,这份资料聚焦SEMI标准体系下的设备安全规范,重点收录SEMI S8-0308《半导体制造设备人体工程学工程安全指南》。内容围绕设备设计原则、错误与事故预防、人体工程学适配三大主线展开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑