perf(worker): 「我的订单」列表改成先取 id 再取详情,排序阶段不再物化宽列
打手端我的订单的排序是表达式:
COALESCE(CASE WHEN wo.assigned_worker_id = $1 THEN wo.assigned_at END,
CASE WHEN worker_share.status != 'cancelled' THEN worker_share.created_at END)
DESC NULLS LAST, wo.id DESC
它依赖打手参数与拼单表,**没有任何索引能支撑**,规划器只能把该打手全部可见单
连同宽列(每行约 1.2KB material_json)物化后排序。生产实测(真实 service 路径):
名下 6609 单的打手首访 1112ms、5198 单的 808ms、3140 单的 772ms,每次读约 60MB
(列表缓存 2 秒一失效,所以这个代价每天要重复付很多次)。
改法:该排序走"延迟分页"——第一阶段只 SELECT wo.id(窄投影 + 同一 ORDER BY +
同一 LIMIT/OFFSET),第二阶段用 `WHERE wo.id = ANY($1::bigint[])` 取这一页的详情,
再按 id 顺序还原(ANY 不保证顺序)。行集、顺序、总数语义完全不变。
验证:后端 542 项单测全绿(新增 1 项守卫);生产上先用部署前的 id 顺序留底,
部署后用同一批打手比对首 20 条 id 与总数,并对比首访耗时。
This commit is contained in:
@@ -2787,3 +2787,24 @@ test('统计的「状态+日期范围」组合必须有 created_at 键的覆盖
|
||||
'迁移在事务内执行,不能使用 CONCURRENTLY',
|
||||
)
|
||||
})
|
||||
|
||||
test('打手端「我的订单」必须走"先取 id 再取详情"的延迟分页', async () => {
|
||||
const { readFile } = await import('node:fs/promises')
|
||||
const src = await readFile(
|
||||
new URL('../../repositories/worker-platform/work-order-query-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(
|
||||
src.includes("sort === 'worker_claimed_at_desc' && workerSharingId && !pinnedFirst"),
|
||||
'缺少打手排序的延迟分页分支:该排序是表达式,无索引可支撑,会物化全部可见单的宽列',
|
||||
)
|
||||
assert.ok(
|
||||
src.includes('SELECT wo.id'),
|
||||
'第一阶段必须只取 id(窄投影),否则排序仍会读 material_json',
|
||||
)
|
||||
assert.ok(
|
||||
src.includes('WHERE wo.id = ANY($1::bigint[])'),
|
||||
'第二阶段必须按 id 批量取详情,而不是重新跑一遍带排序的宽查询',
|
||||
)
|
||||
assert.ok(src.includes('rowsById.get(id)'), 'ANY() 不保证顺序,必须按 id 顺序还原分页结果')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user