fix(media): upsert_media MySQL 悬空 new.fetched_at——录音本地化停更多日

第二轮同步审计实测复现(最小用例 0.3s 即炸):upsert_media 的 update_cols
取了全部表列,而 values 只含 11 个业务列——server_default 列
(fetched_at/updated_at)不在 INSERT 列集,MySQL 编译 ON DUPLICATE 时生成
new.fetched_at 悬空引用 → 1054 Unknown column → 每夜 full 的
call_recordings 步骤(录音下载本地化)自生产部署起**每夜静默失败**,
新录音一直没本地化(异常被 SyncRun 吞成 failed 日志,序列继续)。

修:update_cols 只取 values 里实际有的列(对齐 _build_upsert_stmt 的
「只更新实际插入列」原则);updated_at 在冲突重下时刷 now()。

SQLite 分支(excluded 引用宽容)同逻辑修正;722 测试绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-10 11:54:14 +08:00
parent b72e957da2
commit 4e53a99629
+9 -2
View File
@@ -803,8 +803,13 @@ async def upsert_media(
dialect = await _dialect_name(session)
pk_cols = _pk_col_names(Media)
# 只更新实际插入的列(2026-09-10 生产修复):此前取全部表列,server_default
# 列(fetched_at/updated_at)不在 values 里 → MySQL 编译出
# ``new.fetched_at`` 悬空引用 → 每夜 call_recordings 步骤 1054 静默
# 失败,录音本地化停更多日。
update_cols = [
c.name for c in Media.__table__.columns if c.name not in pk_cols
c.name for c in Media.__table__.columns
if c.name not in pk_cols and c.name in values
]
media_cols = Media.__table__.c
@@ -812,7 +817,8 @@ async def upsert_media(
"""Map each updated column to its conflict-set expression.
Volatile download artifacts use ``COALESCE(incoming, existing)``; every
other column is refreshed directly from the incoming row.
other column is refreshed directly from the incoming row. ``updated_at``
刷新为 now()(冲突重下时反映最新一次落盘)。
"""
set_: dict[str, Any] = {}
for name in update_cols:
@@ -820,6 +826,7 @@ async def upsert_media(
set_[name] = sa.func.coalesce(other_cols[name], media_cols[name])
else:
set_[name] = other_cols[name]
set_["updated_at"] = sa.func.now()
return set_
if dialect == "sqlite":