perf(worker): 单号检索收敛为可走索引的等值/前缀,并支持关联单号反查

问题:后台「按单号检索」在平台单号/工单号分支里 OR 了一支
`work_order_no ILIKE '%'||$n||'%'`。前置通配符非 sargable,任何 B-Tree 都用不上,
整条查询退化成 work_orders 的 Seq Scan。

生产实测(10.3 万行 / 572MB,pg_stat_statements 46 小时窗口):
  - 列表查询 + 统计查询用同一份 filters 并行执行,一次检索打两遍全表;
    两者合计占全库 66% 的磁盘读(1.1TB/46h),单次读 26,442 个块(约 211MB)、398ms、返回 1 行。
  - 收敛后同库同参数:等值 0.19ms,前缀 30ms(约 2000 倍)。
  - 真实统计查询端到端 760ms → 61.6ms。

改动:
1. 平台单号/工单号分支收敛为「等值 + 前缀」,与 order-repo / task-repo 既有写法一致;
   工单号含随机小写段(10.2 万行里 7.4 万行非全大写),保留 LOWER() 比较。
2. 迁移 090 补 text_pattern_ops 前缀索引:当前库 collation 为 en_US.utf8,
   前缀 LIKE 在默认 B-Tree 上会退化成全索引扫(实测 438ms)。
3. 新增关联单号反查分支:SUB/INV/AS/DT 不在 work_orders 上,
   经关联表反查工单 id,四条链路均命中唯一索引。
4. 前端 isExplicitOrderNoSearch 同步补齐(否则搜到的单会被状态页签挡掉、
   实时补丁还会把行删掉)。

验证:生产 EXPLAIN 逐条比对 + 14 项仓储单测 + 6 项分类器单测。
This commit is contained in:
yml2213
2026-10-03 14:27:32 +08:00
parent f0d745ea50
commit 90de6b9419
8 changed files with 289 additions and 6 deletions
@@ -0,0 +1,29 @@
-- 090_work_order_lookup_pattern_indexes.sql —— 后台「按单号检索」的前缀索引缺口。
--
-- 背景:work-order-query-repo 的单号检索分支曾经 OR 了一支 `work_order_no ILIKE '%'||$n||'%'`,
-- 前置通配符非 sargable,把整条查询拖成 work_orders 的 Seq Scan。该 SQL 已在代码侧收敛为
-- 「等值 + 前缀」两段(与 order-repo / task-repo 一致),但前缀段还需要本迁移的索引支撑。
--
-- 生产实测(2026-10-03,10.3 万行 / 572MB,datcollate = en_US.utf8):
-- 收敛前:Seq Scan,Rows Removed by Filter: 102,631,26,442 buffers(≈211MB),398ms
-- pg_stat_statements 46 小时窗口里,同一形状的列表查询 + 统计查询合计占全库 66% 磁盘读
--
-- 为什么不能直接用已有索引:
-- idx_work_orders_platform_order / work_orders_work_order_no_key 都是默认 collation 的
-- B-Tree,而当前库 collation 为 en_US.utf8,前缀 LIKE 无法转换成范围条件 —— 实测
-- `platform_order_id LIKE '262740%'` 退化成 Index Only Scan 全索引 + Rows Removed by
-- Filter: 99,471,438ms,几乎等于全表扫。text_pattern_ops 才能让前缀走真正的范围扫描。
CREATE INDEX IF NOT EXISTS idx_work_orders_platform_order_pattern
ON work_orders (platform_order_id text_pattern_ops);
-- 工单号必须大小写不敏感:10.2 万行里有 7.4 万行的随机段含小写(生成器见 utils/random.ts),
-- 所以检索条件用 LOWER(work_order_no) 比较,索引也必须建在同一个表达式上。
-- 实测:等值命中 0.048ms(Bitmap Index Scan),前缀 'WO2610' 走 Bitmap Heap Scan 30~234ms
-- (较收敛前的 398ms Seq Scan 已是量级改善,且不再产生 200MB 级缓冲读)。
CREATE INDEX IF NOT EXISTS idx_work_orders_work_order_no_lower_pattern
ON work_orders (LOWER(work_order_no) text_pattern_ops);
COMMENT ON INDEX idx_work_orders_platform_order_pattern IS
'后台平台单号前缀检索:en_US.utf8 collation 下前缀 LIKE 需要 text_pattern_ops 才能走范围扫';
COMMENT ON INDEX idx_work_orders_work_order_no_lower_pattern IS
'后台工单号大小写不敏感的等值/前缀检索(工单号随机段含小写)';