perf: 筛选统计去掉 JSON 兜底,并补上 orders 覆盖索引

去掉 work_orders 堆访问后,JOIN 会把 orders 整表扫进 Hash Right Join。
领取页在快照新鲜时不再打上游;快手出网统一 10 秒超时。倒计时改为自适应间隔。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-10-01 15:05:38 +08:00
co-authored by Cursor
parent f4c405d51a
commit 1239393fc2
20 changed files with 404 additions and 65 deletions
@@ -333,31 +333,26 @@ async function countWorkOrdersWithWhere(
/** 按筛选条件汇总工单,统计范围不受当前分页影响。 */
/**
* 统计用的「用户付款金额」表达式。
* 统计用的「用户付款金额」。
*
* 优先读 083 迁移落下来的派生列 `source_order_amount_fen`(写入点:createWorkOrder /
* updateWorkOrder,口径见 shared.ts 的 resolveSourceOrderAmountFen)。
* **COALESCE 是短路求值**:列非空时后面两个分支根本不会被计算,所以命中列的行完全不碰
* material_json——这正是把 2.8 秒的统计查询拉回毫秒级的关键(改造前每行要解析 1.2KB JSON,
* 全表约 132MB,而且这个表达式在总付款与退款两处各求值一次)。
* 只读 083 的派生列 `source_order_amount_fen`(写入点:createWorkOrder / updateWorkOrder,
* 口径见 shared.ts 的 resolveSourceOrderAmountFen)。NULL 表示来源没给合法整数,
* 回退 `orders.total_amount`,与 083 的列注释一致。
*
* 第二、三个分支是兜底,只为覆盖"写入点漏了"或迁移前的老行,保证口径永远正确:
* 1) 派生列为 NULL 且 JSON 里有 15 位以内的合法整数 → 用 JSON 值;
* 2) 否则回退 orders.total_amount(与改造前完全一致)。
* 这里不能再写 `material_json`。执行器对 COALESCE 会短路,但规划器在规划期就记下列引用,
* 只要 SQL 文本里出现这列,覆盖索引就做不了 index-only scan,9.7 万行宽表仍会被堆访问
* (生产实测缓存未命中 248–545MB / 约 400ms)。漏写不在热路径上用 JSON 兜底,
* 交给数据保留任务回填并打日志。
*/
const WORK_ORDER_PAYMENT_AMOUNT_SQL = `
COALESCE(
wo.source_order_amount_fen,
CASE
WHEN COALESCE(wo.material_json->'source'->>'orderAmountFen', '') ~ '^-?[0-9]{1,15}$'
THEN (wo.material_json->'source'->>'orderAmountFen')::bigint
END,
COALESCE(o.total_amount, 0)
)
COALESCE(wo.source_order_amount_fen, o.total_amount, 0)
`
export async function getWorkOrderStatistics(input: ListInput = {}): Promise<WorkOrderStatistics> {
const { joins, whereClause, params } = buildWorkOrderWhere(input)
// 仅勾「VIP 待补图」时才拼进 SQL。087 的覆盖索引故意不含 acceptance_json:
// 这个筛选项会让统计回表变慢,是有意降级。不要把 acceptance_json 补进索引,
// 否则默认统计(不勾这个筛选)会退回宽表堆访问。
const vipEvidencePendingWhere = `
wo.status = 'accepted'
AND wo.acceptance_json->>'reviewMode' = 'vip_auto'