ScanningReporting 速度优化
admin |
2026-06-03 19:58 |
阅读 9 次
优化 GME ScanningReport 一天数据查询性能
Summary
当前慢点主要有三层叠加:大范围 physical_layers 扫描、逐 panel 读网络 .proto 文件、逐 defect 的 N+1 MySQL 明细查询。
本次优化目标按你的选择定为:优先降低总耗时,同时保持结果完全一致。
预计收益最大的改动顺序是:
- 去掉逐 defect 的 N+1 查询,改成批量明细预取。
- 初始 panel 查询不再携带
pl.Container大字段,改成后置按需批量加载。 - 为首个时间范围查询补充索引检查/建议,减少一天数据扫描量。
Key Changes
1. 拆分查询为 3 个阶段
- 阶段 A:先查询 panel 基础元数据,只取
Id/Scanned/Verified/AOI/CVR/JobName/LayerName/LotName/PhysicalID/MovesCount/DefectsCount,不取pl.Container。 - 阶段 B:遍历 panel 读取
.proto,收集所有需要的 defect 键:(StepInstanceId, OriginalCodeId, CodeId)。 - 阶段 C:按批次一次性回库加载 defect 明细,再组装最终
ScanningReportItem。
2. 消除 LoadDefectDetail(...) 的 N+1 查询
- 删除“每个 defect 查询一次 MySQL”的实现。
- 改为在同一个连接内创建临时表,例如
tmp_scanning_defect_keys,列为:StepInstanceIdOriginalCodeIdCodeId
- 将阶段 B 收集到的唯一键去重后,分批插入临时表。
- 用一次 join 查询取回全部
UnitID/SetID/SparkCode/DefectCode/DefectDescription,结果放入内存字典,key 为三元组。 - 明细字典缺失时,保持当前行为:该 defect 跳过,不改变最终结果语义。
3. 将 Container 改为后置按需批量加载
- 初始
physical_layersSQL 去掉pl.Container as Container。 - 在
.proto读取完成、确定该 panel 最终会产出结果后,再按 surviving panel 的Id分批查询:select id, Container from physical_layers where id in (...)
- 分批大小固定为
500。 - 将
ProcessId/Barcode的反序列化改为基于这个后置批量结果完成,输出字段保持不变。
4. SQL 过滤前移
- 如果
OnlyOver24Hours = true,把条件前移到首个 SQL:pl.Verified is not nulltimestampdiff(hour, pl.Scanned, pl.Verified) >= 24
LotId/LayerId/PanelPhysicalId继续保留在首个 SQL 中。IncludeZeroDefectPanels先不下推到 SQL,避免依赖DefectsCount字段语义与.proto实际内容完全一致。
5. 数据库索引建议
- 对
physical_layers检查并补充复合索引,优先顺序:(Scanned, LotID, LayerInfoID, PhysicalID)
- 如果
PanelPhysicalId高频单独查询,额外考虑:(PhysicalID, Scanned)
- 对
steps_instances.id、steps_instances.ParentID、customer_defects_codes.id确认已有索引;如果是主键则无需额外处理。 - 实施前先跑
EXPLAIN,确认首个查询命中Scanned相关索引,而不是全表扫描。
Implementation Notes
- 主要改动集中在 ScanningReportService.cs。
PhysicalLayerRow去掉Container字段,新增后置容器加载用的内部 DTO 或字典。- 新增一个批量明细加载方法,替代
LoadDefectDetail(...)。 - 进度状态拆为更真实的阶段:
Loading panel list...Reading proto files...Loading defect details...Loading container data...Building result...
- 临时表在单次查询连接内创建和使用,查询结束自动销毁,不引入持久 schema 变更。
Test Plan
- 查询 1 天数据,结果行数与当前版本一致。
- 随机抽样 20 条结果,对比
SetID/UnitID/SparkCode/DefectCode/DefectDescription/ProcessId/Barcode与旧逻辑一致。 - 验证
OnlyOver24Hours打开/关闭时,结果与旧逻辑一致。 - 验证
IncludeZeroDefectPanels打开/关闭时,结果与旧逻辑一致。 - 验证缺失
.proto、损坏.proto、缺失 defect detail 三种异常场景仍不崩溃,行为与当前版本一致。 - 对“一天数据”记录总耗时,重点比较:
- 首屏开始处理时间
- MySQL 查询次数
- 总完成时间
Assumptions
- 保持现有结果列和最终输出完全一致,不做 UI 或结果模型调整。
- MySQL 允许会话级临时表。
ProcessId/Barcode仍然必须保留,因此Container不能彻底移除,只能后置批量加载。- 先做代码层优化;数据库索引如果需要新增,再作为同一轮性能优化的一部分执行。
已经把这轮性能优化落地在 ScanningReportService.cs。
现在查询链路变成了:
physical_layers时不再带pl.Container大字段.protoContainer,再反序列化ProcessId/BarcodeOnlyOver24Hours的过滤前移到了首个 SQL 里Loading panel list / Reading proto files / Loading defect details / Loading container data / Building result我还顺手把临时表初始化改成了两条独立 SQL,避免 MySQL 连接不支持多语句执行的兼容性问题。解决方案已经重新编译通过,
0 error / 0 warning。这次我没有直接改数据库索引,因为那属于真实库变更,不适合在代码仓里直接替你执行。如果你要,我下一步可以继续帮你整理一份可直接在 MySQL 上跑的
EXPLAIN和索引 SQL。