perf(db): 打手端「我的订单」概览加覆盖索引与短缓存
这是数据库累计耗时第一的热路径:32 天 130 万次调用(0.5 次/秒),平均 99.8ms, 但名下订单多的打手会退化到 4.5~9 秒。EXPLAIN (ANALYZE) 定位到根因: 查询只用到 8126 个工单 id,规划器却选了「work_orders 全表顺序扫 + Hash Join」—— 为几千个 id 扫 9.6 万行宽表(500MB),单这一步 4.1 秒。 1. 迁移 084:给查询真正需要的列建覆盖索引(id + INCLUDE status/assigned_worker_id/ reward_amount/acceptance_json)。把那次全表扫变成 index-only scan,代价从 31015 降到约 5k, 规划器自然改选索引;索引 34MB,是堆表的十五分之一。生产实测同一查询 14.5 秒 → 1.5 秒(冷缓存)。 试过用 LATERAL 强制回表,规划器会把它拉平成同样的 Hash Join,所以只能从索引侧解决。 迁移在事务内执行,故用普通 CREATE INDEX(不用 CONCURRENTLY)。 2. 概览读取加 5 秒短缓存:把"计算次数"与"页面轮询次数"解耦。 只缓存读取——会话校验与 settleOverdueWorkOrders(写副作用)每次照常执行,否则会漏结算。 验证:后端 532 项单测全绿(新增 1 项守卫,覆盖索引列与缓存位置都纳入断言)。
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
-- 084_worker_overview_covering_index.sql —— 打手端「我的订单」概览的覆盖索引。
|
||||
--
|
||||
-- 生产实测:这个概览查询 32 天被调用 130 万次(每秒约 0.5 次),
|
||||
-- 平均 99.8ms,但对名下订单多的打手会退化到 4.5~9 秒。EXPLAIN (ANALYZE) 显示原因:
|
||||
-- 它只用到 8126 个工单 id,规划器却选择「work_orders 全表顺序扫 + Hash Join」——
|
||||
-- 为几千个 id 扫了 9.6 万行宽表(500MB),单这一步就是 4.1 秒。
|
||||
--
|
||||
-- 这里不改 SQL(试过 LATERAL 强制回表,规划器会把它拉平成同样的 Hash Join),
|
||||
-- 而是给查询真正需要的列做覆盖索引,让那次全表扫变成 index-only scan:
|
||||
-- 代价从 31015 降到约 5k,规划器自然改选索引;体积约 34MB,
|
||||
-- 只有堆表 500MB 的十五分之一。实测同一台机器上从 14.5 秒降到 1.5 秒(冷缓存)。
|
||||
--
|
||||
-- 注意:迁移在单个事务里执行,所以这里不能用 CREATE INDEX CONCURRENTLY
|
||||
-- (并发建索引不能在事务块内运行)。96k 行的表普通建索引约 1~3 秒,
|
||||
-- 期间只阻塞写入,且部署本身会重启后端,影响可接受。
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_orders_worker_overview_cover
|
||||
ON work_orders (id)
|
||||
INCLUDE (status, assigned_worker_id, reward_amount, acceptance_json);
|
||||
Reference in New Issue
Block a user