Commit 3d02ac03 by luoqi

perf(admin): GET /admin/host/self 卡 28s —— 补 2 个复合索引

getStats() 并发跑 10 条查询,其中两条缺覆盖索引:
- patient_transactions.count(hostId, occurredAt>=X):(patient_id, occurred_at)
  索引带不动"只按时间不带 patient_id"的过滤,近乎全扫索引。线上冷执行 8.3s。
- patient_facts.count(hostId, status='active'):(hostId, tenantId) 没 status、
  (patientId, status) 没 hostId,两个都覆盖不了。线上冷执行 18.9s。
  三列(非二列):tenant-guard 扩展对未带 tenantId 的查询自动注入 tenantId 过滤,
  二列索引配上这条自动追加的谓词后 planner 直接弃用索引退回全表扫,三列才稳定命中
  index-only scan。

两条索引都用 CREATE INDEX CONCURRENTLY(表已 379万/371万行,避免锁表阻塞摄入写入)。
线上手动建索引 + 验证已完成(index-only scan,查询从 8~19s 降到 <1s;端到端
GET /admin/host/self 28s → 0.9s);IF NOT EXISTS 保证下次 `prisma migrate deploy`
在 pac-migrate 里重跑时是空操作,只补 `_prisma_migrations` 记录。
parent 5f063b0a
Pipeline #3335 failed in 0 seconds
-- CreateIndex
-- CONCURRENTLY:表已 379 万行,普通 CREATE INDEX 会 ACCESS EXCLUSIVE 锁全表(阻塞线上写入摄入);
-- CONCURRENTLY 不能跑在事务里,Prisma migrate 对整份 migration.sql 有事务包裹,故本条不能靠
-- `prisma migrate deploy` 直接跑 —— 部署时改为手动 psql 执行 + `prisma migrate resolve --applied`。
CREATE INDEX CONCURRENTLY IF NOT EXISTS "patient_transactions_host_id_occurred_at_idx" ON "patient_transactions"("host_id", "occurred_at");
-- CreateIndex
-- CONCURRENTLY:表已 371 万行,理由同 20260701060607(不锁表);部署时手动 psql 先建 + resolve --applied。
CREATE INDEX CONCURRENTLY IF NOT EXISTS "patient_facts_host_id_tenant_id_status_idx" ON "patient_facts"("host_id", "tenant_id", "status");
......@@ -482,6 +482,9 @@ model PatientTransaction {
@@index([hostId, tenantId, clinicId])
/// 按患者拉时间轴(parser / 审计反查路径)
@@index([patientId, occurredAt])
/// host 运行状态统计(admin/host/self 24h/30d 计数)—— (patientId, occurredAt) 的第二列在
/// "只按时间过滤不带 patientId"时用不上(需近乎全扫索引),线上实测单次 count 冷时 8s+
@@index([hostId, occurredAt])
/// 按业务主体反查(同一 apt_001 被几次改过)
@@index([subjectType, subjectId])
/// 按动作类型分析 / 仪表盘
......@@ -617,6 +620,12 @@ model PatientFact {
@@unique([hostId, tenantId, sourceUnit, subjectId, version])
/// 隔离基础过滤
@@index([hostId, tenantId])
/// host 运行状态统计(admin/host/self active fact 计数)—— (hostId, tenantId) status,
/// (patientId, status) 不带 hostId;两者都覆盖不了"按 host+status 计数",线上实测冷扫全表 19s
/// 三列(非仅 hostId+status):tenant-guard 扩展( tenant-guard.extension.ts)会给未带 tenantId
/// 的查询自动注入 tenantId 过滤——实测二列索引配上这条自动追加的谓词后,planner 直接弃用索引
/// 退回全表扫;三列精确覆盖实际执行的查询形状,才稳定命中 index-only scan
@@index([hostId, tenantId, status])
/// 拉某患者当前 active 视图(persona / plan 重算主路径)
@@index([patientId, status])
/// 召回 selectHits 主路径:sig 信号扫描 + gap resolvedTeeth 各子查询都按 (patient_id, type, status) 过滤。
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment