# 2026-08-29 工作日志 ## 修复 Oracle→NAS 增量同步 1062 (uq_video_raw_uid 重复键) — 已部署验证 ### 现象 - 同步状态页:状态"同步中"、`最近 —`、`本次增量 —`、游标卡在 `2026-08-29 04:57:15` - 报错:`(1062, "Duplicate entry '1073-汤圆' for key 'uq_video_raw_uid'")`,游标永不推进 ### 根因(两层) 1. **镜像表主键设计缺陷(主因)**:`sync_identity_map` 以 Oracle `person_identity_map.id` 作主键。 Oracle 端 id 是 AUTOINCREMENT 代理键,但 Oracle 库重建/恢复后 id 会被复用——实测 Oracle 现在的 id=744 是 `(1073,'汤圆')`,而 NAS 上 id=744 还是旧的 `(1302,'人物E')`。旧 upsert 的 `ON DUPLICATE KEY UPDATE` 先按 **PK id** 命中旧行,再把它 UPDATE 成 `(1073,'汤圆')`,与 已有的 `(1073,'汤圆')` 行在 `uq_video_raw_uid` 上二次冲突 → 1062 → 整批失败 → 游标不推进 → 每 30 分钟重复失败。 2. **陈旧 __pycache__(部署陷阱)**:NAS 上 `.pyc` 时间戳比 `.py` 新(部署时 `.py` 带旧 mtime 拷贝), Python 信任 pyc 加载了旧逻辑;另曾有两个 gunicorn master 并存(8487/10914),只有一个绑 8000。 ### 修复 - **db_layer.py `upsert_sync_identity_map` v2**:镜像表改为 NAS 本地自增 `id` 主键, 业务键 `(video_id, raw_uid)` 唯一;Oracle id 只落 `oracle_id` 列溯源;UPDATE 子句不再改写 video_id/raw_uid(只更新 oracle_id/canonical_name/source/updated_at/synced_at)。 - **scripts/ddl.sql**:同步更新表定义(`id INT AUTO_INCREMENT PRIMARY KEY` + `oracle_id INT`)。 - **现网迁移**:`ADD COLUMN oracle_id` → 回填 `oracle_id=id` → `MODIFY id AUTO_INCREMENT`(无重复对, 迁移安全);修复早前测试误改的 3061 行 canonical_name。 - **部署**:db_layer.py 用 stdin 管道覆盖 NAS;**清空全部 __pycache__**;杀掉双 gunicorn,单实例重启。 ### 验证(通过) - 09:55 一次补拉成功:`videos+260 events+1456 people+37 model_calls+744 identity_map+285`,游标推进到 09:55:06 - `/api/status`:`last_error=null`、`last_sync_at` 有值;手动 `/api/sync/trigger` ok(拉 2videos/2events/1people/6calls/1identity) - `(1073,'汤圆')` 行已自愈:`oracle_id=744`;NAS 旧 `(744→1302,'人物E')` 保留不冲突 ### 待办/注意 - 本地 repo 有未提交改动:`fam-core/src/fam_core/db_layer.py`、`scripts/ddl.sql`(等用户拍板提交) - 同类隐患:sync_videos/sync_events/sync_model_calls 同样以 Oracle id 为主键,但 Oracle 侧这些表 只插入不 UPDATE、id 稳定,风险低;若将来 Oracle 也重建,需同样改法 - NAS 部署后必须清 __pycache__(或 touch .py),否则旧 pyc 被加载(已踩坑)