perf(worker): 「仅问题单」检索短路,从 pending 表反查而非枚举可见集

问题单页签原先走完整派生表达式:为该打手枚举全部可见单并逐行求值
嵌套 CASE + pending EXISTS(重打手 8,586 单 × 逐行探测 ≈ 233MB 缓冲读),
而问题单结果通常为空——生产实测 worker_order_cancel_requests 与
worker_order_feedbacks 的 pending 行数当前均为 0。

短路形状:
  wo.status = 'problem'
  OR (基础状态 = 'in_progress' AND wo.id IN (两张 pending 表反查))

- 左臂必须写成 sargable 的 wo.status = 'problem':基础状态等于 'problem'
  当且仅当 wo.status = 'problem'(派生分支没有 problem 映射);
- 反查走迁移 058 的两张 pending 专用索引,实测 8 个块;
- 仅当请求状态"只含 problem"时短路,混合状态仍走完整表达式(结果集完整性);
- 顺带把 workerOrderStatusExpression 拆出 workerOrderBaseStatusExpression 复用。

生产 EXPLAIN(worker=7):29,851 块 / 267ms → 13,387 块 / 118ms。
This commit is contained in:
yml2213
2026-10-03 15:22:57 +08:00
parent 253dc2061e
commit fbdef6ecab
2 changed files with 87 additions and 14 deletions
@@ -173,6 +173,34 @@ test('普通关键词仍然走 trgm 表达式模糊搜索', () => {
assert.deepEqual(query.params, ['%指挥官密钥%'])
})
test('「仅问题单」检索走短路分支,从 pending 表反查而非枚举可见集', () => {
const query = buildWorkOrderWhere({ workerSharingId: 7, statuses: ['problem'] })
// 短路形状:sargable 的直查 + 从两张极小的 pending 表反查
assert.match(query.whereClause, /wo\.status = 'problem'/)
assert.match(query.whereClause, /FROM worker_order_cancel_requests/)
assert.match(query.whereClause, /FROM worker_order_feedbacks/)
assert.match(query.whereClause, /status = 'pending'/)
// 不得再生成完整派生表达式(那会强制对该打手全部可见单逐行求值)
assert.doesNotMatch(query.whereClause, /THEN 'problem'\s+ELSE CASE/)
// 第一个 7 来自 worker_visible 连接,第二个 7 是短路分支的打手 id
assert.deepEqual(query.params, [7, 7])
})
test('问题单与其它状态混合时不得短路,必须走完整派生表达式', () => {
const query = buildWorkOrderWhere({ workerSharingId: 7, statuses: ['problem', 'in_progress'] })
assert.match(query.whereClause, /= ANY\(\$\d+::text\[\]\)/, '混合状态必须用完整表达式')
// 完整表达式的 EXISTS 里也会出现这两张表,用短路分支独有的「SELECT work_order_id + UNION」
// 形状来判别,确保混合状态没有误入短路
assert.doesNotMatch(
query.whereClause,
/SELECT work_order_id\s+FROM worker_order_cancel_requests/,
'混合状态误入短路分支会导致结果集不完整',
)
assert.deepEqual(query.params, [7, ['problem', 'in_progress']])
})
test('单号检索分支不得回退成裸列模糊匹配(源码级防退化守卫)', async () => {
const { readFile } = await import('node:fs/promises')
const src = await readFile(
@@ -192,12 +192,19 @@ function buildRelatedWorkOrderLookupFilter(
}
/**
* 打手订单个人状态:拼单按个人份额状态计算。
* 传入 workerId 时,代练中但存在待审核取消申请或未处理反馈的订单归入问题单,
* 客服驳回/解决后自动回到代练中(计算型归类,无状态迁移)。
* 是否为「仅问题单」筛选。前端问题单页签固定传单个 problem;
* 一旦混入其它状态就必须走完整派生表达式,不能短路。
*/
export function workerOrderStatusExpression(workerId = 0, workerShareAlias = 'worker_share') {
const baseExpression = `CASE
function isProblemOnlyStatusFilter(statuses: string[]): boolean {
return statuses.length === 1 && statuses[0] === 'problem'
}
/**
* 打手订单基础状态:拼单按个人份额状态计算(不含"问题单"派生)。
* 拆出来是因为「仅问题单」检索可以用它做短路,不必派生完整表达式。
*/
export function workerOrderBaseStatusExpression(workerShareAlias = 'worker_share') {
return `CASE
WHEN wo.status IN ('accepted', 'cancelled', 'problem') THEN wo.status
ELSE COALESCE(CASE ${workerShareAlias}.status
WHEN 'joined' THEN 'in_progress'
@@ -207,6 +214,15 @@ export function workerOrderStatusExpression(workerId = 0, workerShareAlias = 'wo
ELSE wo.status
END, wo.status)
END`
}
/**
* 打手订单个人状态:拼单按个人份额状态计算。
* 传入 workerId 时,代练中但存在待审核取消申请或未处理反馈的订单归入问题单,
* 客服驳回/解决后自动回到代练中(计算型归类,无状态迁移)。
*/
export function workerOrderStatusExpression(workerId = 0, workerShareAlias = 'worker_share') {
const baseExpression = workerOrderBaseStatusExpression(workerShareAlias)
const normalizedWorkerId = Math.max(0, Math.floor(Number(workerId) || 0))
if (normalizedWorkerId <= 0) return baseExpression
const pendingHandlingExists = `(
@@ -963,16 +979,45 @@ export function buildWorkOrderWhere({
filters.push(`(${vipEvidencePendingWhere})`)
}
} else if (normalizedStatuses.length > 0) {
params.push(normalizedStatuses)
const statusesPlaceholder = `$${params.length}`
if (workerSharingId) {
filters.push(
`${workerOrderStatusExpression(workerSharingId)} = ANY(${statusesPlaceholder}::text[])`,
)
if (workerSharingId && isProblemOnlyStatusFilter(normalizedStatuses)) {
// 「仅问题单」短路:问题单 = wo.status 直接是 problem,或 进行中 且挂着
// 待审核取消申请/未处理反馈。两条 pending 表极小(生产实测常为 0 行),
// 从它们出发反查,避免为了一个通常为空的结果枚举该打手全部可见单再逐行求值
// 完整 CASE(重打手 8,586 单 × 逐行探测 ≈ 233MB 缓冲读)。
// 注意左臂必须写成 sargable 的 wo.status = 'problem':完整表达式里
// 基础状态等于 'problem' 当且仅当 wo.status = 'problem'(派生分支没有 problem 映射)。
params.push(workerSharingId)
const workerIdPlaceholder = `$${params.length}`
const baseExpression = workerOrderBaseStatusExpression('worker_share')
filters.push(`(
wo.status = 'problem'
OR (
${baseExpression} = 'in_progress'
AND wo.id IN (
SELECT work_order_id
FROM worker_order_cancel_requests
WHERE worker_id = ${workerIdPlaceholder}
AND status = 'pending'
UNION
SELECT work_order_id
FROM worker_order_feedbacks
WHERE worker_id = ${workerIdPlaceholder}
AND status = 'pending'
)
)
)`)
} else {
filters.push(
`${useOperationalStatus ? adminOperationalStatusExpression('wo', 'operational_share_summary') : 'wo.status'} = ANY(${statusesPlaceholder}::text[])`,
)
params.push(normalizedStatuses)
const statusesPlaceholder = `$${params.length}`
if (workerSharingId) {
filters.push(
`${workerOrderStatusExpression(workerSharingId)} = ANY(${statusesPlaceholder}::text[])`,
)
} else {
filters.push(
`${useOperationalStatus ? adminOperationalStatusExpression('wo', 'operational_share_summary') : 'wo.status'} = ANY(${statusesPlaceholder}::text[])`,
)
}
}
}
if (workOrderId) {