fix(fam-core): 修复 sync_identity_map 增量同步 1062 主键冲突导致游标卡死

Oracle→NAS 增量同步反复报 (1062, "Duplicate entry '1073-汤圆' for key
'uq_video_raw_uid'"),整批失败、游标永不推进,同步状态页卡在"同步中"。

根因:sync_identity_map 以 Oracle person_identity_map.id(代理自增键)
作 NAS 主键,但 Oracle 库重建/恢复后这个 id 会被复用——实测 Oracle 现在
的 id=744 对应 (1073,'汤圆'),NAS 上 id=744 还是旧的 (1302,'人物E')。
upsert 先按 PK id 命中旧行,UPDATE 成 (1073,'汤圆') 后与已存在的同名行
在 uq_video_raw_uid 上二次冲突。

修复:镜像表改为 NAS 本地自增 id 主键,业务键 (video_id, raw_uid) 唯一,
Oracle 的 id 只落 oracle_id 列做溯源;upsert 的 UPDATE 子句不再改写
video_id/raw_uid 这两个键列,只更新 oracle_id/canonical_name/source 等。
ddl.sql 同步更新表定义。

已在 NAS 上完成现网迁移(ADD COLUMN oracle_id -> 回填 -> 主键改自增)并
验证:补拉同步成功恢复,游标正常推进,手动触发同步验证通过。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-08-29 23:10:38 +08:00
parent 05c7a8b70a
commit 988c63a8f9
5 changed files with 60 additions and 6 deletions

View File

@@ -14,3 +14,7 @@
- 孤儿云库 sentinel_home_ai + famcore 用户已 DROP新服务器
- SS Webhook代码中为可选补充motion_bp.py 文档明确SS 未配行动规则故休眠,
是否删除待用户拍板。
- **用户拍板:删除 webhook + NAS 前端托管,并已提交推送**(两个 commit 已 push 到 Gitea
`7948f55` docs 迁移文档、`cf3d145` refactor 移除 webhook/static_app 共删 329 行)。
**待办:代码尚未部署到 NAS**——需同步 fam-core 到 NAS、删 NAS fam-ui 目录 + start_ui.sh、
重启 fam-core 验证 /api 与登录(云端 :80 前端不受影响)。

View File

@@ -0,0 +1,37 @@
# 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 被加载(已踩坑)

View File

@@ -27,6 +27,12 @@
素材 `/opt/fam-edge/gdrive_videos`logs 目录 ubuntu 可写。
**前端亦托管于此机 Caddy :80**fam-ui SPA + /api 反代回 NAS fam-core :8000 经 frp 隧道);旧机 129.146.203.203 已退役。
- 部署:本地 git 提交 push Gitea → tar 管道NAS 直接解包Oracle `--strip-components=1` 到临时目录再 cp避免动 data/venv
- **NAS 部署坑2026-08-29 踩过)**:拷贝 `.py` 会带旧 mtime`.pyc``.py`Python 加载陈旧字节码
(旧逻辑上线,曾导致 1062 假象)。部署后必须 `find src -name __pycache__ -exec rm -rf {} +` 再重启;
`pkill -f` 会匹配 SSH 自身命令导致断连(用 PID 文件或精确 pattern
- **镜像表键设计2026-08-29 定稿)**`sync_identity_map` 不再以 Oracle `person_identity_map.id` 作主键
Oracle 库重建会复用 id改 NAS 本地自增 `id` + 唯一键 `(video_id, raw_uid)`Oracle id 落 `oracle_id` 列溯源;
同批 upsert 的 UPDATE 子句不改写键列,避免 uq 二次冲突 1062。sync_videos/events/model_calls 同理(风险低未改)。
## 提交规范(强制)
- 格式 `[阶段X.Y子任务号] 子任务名称 - 完成内容简述`Bug 修复 `fix(模块): 问题简述`