Commit bf921bbc by luoqi

fix(sync): 补 patient_transactions 缺失的 source_event_id partial UNIQUE

🔴 预存 bug:schema 注释一直声称有 partial UNIQUE 幂等键,但 init migration
从没真正建过 → source_event_id 幂等从未在 DB 层强制:
  - createMany({skipDuplicates:true}) 一直是空操作(无约束可依据,全插入)
  - per-row create 的 P2002 catch 永不触发
  - 重复导入 / 同源 dup 行 → 产生重复 patient_transactions

发现过程:PR5b 并行测试做完整性自查,发现 12 个重复 source_event_id
(都是 fact_appointment_out 里同 appointment 同时间戳的真重复行)。
查 \d patient_transactions 确认无 unique index,查 init migration 确认从没建。

注:fact 层的 (subject_id, version) UNIQUE 是真建了的,所以 fact 没坏
(自查 multi_active_facts=0 验证),只有 tx 层有重复风险。

migration 20260528000002:
  1. dedup 已有重复(每组保留 event_seq 最小 = 最早写入)
  2. 建 partial UNIQUE (host_id, tenant_id, source_event_id) WHERE NOT NULL

本地验证:
  dedup: 4330 → 4318(删 12 重复),remaining_dups=0
  加约束后重跑并行:txns=4318 dups=12(constraint 正确拦截 12 重复 skip)
  DB 终态:0 dup_source_events / 0 multi_active_facts
  → createMany skipDuplicates 现在真正生效;幂等在 DB 层强制
parent d3635199
-- 补 patient_transactions 缺失的幂等键 partial UNIQUE。
--
-- 背景:schema 注释一直写"MIGRATION partial UNIQUE (host_id, tenant_id, source_event_id)
-- WHERE source_event_id IS NOT NULL",但 init migration 从没真正建过 → 幂等从未在 DB 层强制:
-- · createMany({skipDuplicates:true}) 失效(无约束可依据 → 全插入)
-- · per-row create 的 P2002 catch 永不触发 → 重复导入产生重复 tx 行
-- fact 层的 (subject_id, version) UNIQUE 是真建了的,所以 fact 没坏,只有 tx 层有重复风险。
--
-- 本 migration:
-- 1. 先 dedup 已有重复(保留每组最小 event_seq 的那行 = 最早写入)
-- 2. 再建 partial UNIQUE 索引
--
-- ⚠️ dedup DELETE 会删掉重复 tx 行;这些行衍生的 fact 仍在(fact 按 subject_id+version 去重,
-- 不受影响),只是 fact.transaction_ids 里可能引用被删 tx id(审计软引用,可接受)。
-- 1. dedup:同 (host_id, tenant_id, source_event_id) 保留最早(event_seq 最小)那行
DELETE FROM "patient_transactions" a
USING "patient_transactions" b
WHERE a."source_event_id" IS NOT NULL
AND a."host_id" = b."host_id"
AND a."tenant_id" = b."tenant_id"
AND a."source_event_id" = b."source_event_id"
AND a."event_seq" > b."event_seq";
-- 2. 建 partial UNIQUE(幂等键正式生效)
CREATE UNIQUE INDEX "patient_transactions_source_event_uq"
ON "patient_transactions" ("host_id", "tenant_id", "source_event_id")
WHERE "source_event_id" IS NOT NULL;
...@@ -383,7 +383,9 @@ model PatientTransaction { ...@@ -383,7 +383,9 @@ model PatientTransaction {
@@index([receivedAt]) @@index([receivedAt])
/// 水位游标查询(persona 重算"自上次以来新增的 transaction") /// 水位游标查询(persona 重算"自上次以来新增的 transaction")
@@index([eventSeq]) @@index([eventSeq])
/// MIGRATION:partial UNIQUE (host_id, tenant_id, source_event_id) WHERE source_event_id IS NOT NULL 幂等键 /// 幂等键 partial UNIQUE (host_id, tenant_id, source_event_id) WHERE source_event_id IS NOT NULL
/// Prisma 不支持声明式 partial unique;migration 20260528000002 是单一真理源。
/// (init migration 漏建,20260528000002 dedup + 建索引)
@@map("patient_transactions") @@map("patient_transactions")
} }
......
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