AI多Agent协作系统实战(三十七):任务反复“穿越“回起点——一个没有主键的表

📅 2026/8/13 10:31:35
AI多Agent协作系统实战(三十七):任务反复“穿越“回起点——一个没有主键的表
同一个任务明明开发完成了、测试通过了——第二天一看状态又变回未开始创建时间被改、完成时间被清空。像穿越回了起点。排查了三天最后发现数据库表没有主键——任何人都能往里插一条看起来全新的旧任务。现象任务会穿越我们的多Agent派发系统每个任务在数据库里有一条记录状态流转派发 → 开发完成 → 测试完成 → 复核通过。但那几天任务频繁穿越DEV-011测试完成时间戳已写入→ 几分钟后复核发现测试未完成DEV-015开发完成时间戳已写入→ 复核发现开发未完成DEV-016开发完成 → 状态又变回待开发创建时间变成几分钟前任务明明往前走了却总被打回起点。完成时间被清空、创建时间被改写——就像游戏存档被回滚。一开始怀疑是状态机逻辑时间戳写丢了覆盖了并发竞争修了三轮ws_server的正常写入路径——查了没问题重派逻辑COALESCE保留完成时间——查了没问题超时检测拦截已完成任务——查了没问题所有已知路径都清白但任务就是会被重置。排查一条看起来全新的记录最后我直接查数据库发现一个惊人的细节——被重置的任务to_agent接收者字段是空的task_id to_agent status created_at DEV-20260805-016 NULL pending 14:08:03 ← 这条新记录而正常的任务to_agent应该是小虾或小牛。空的to_agent——这不是原来的那条记录这是一条新插入的赝品创建时间是插入时刻、完成时间是空的——所以看起来像全新任务。再一查整个表里有124 条任务的 to_agent 是空的——全部是被赝品覆盖过的痕迹。根因一张没有主键的表打开建表语句问题一目了然CREATETABLEtasks(task_idTEXT,-- ← 没有 PRIMARY KEYfrom_agentTEXT,to_agentTEXT,...);tasks 表没有主键没有任何唯一约束。理论上可以插入任意多条 task_id 相同的记录。再看出问题的代码——“检查任务是否已存在”# 派发任务前检查是否已存在同名任务existingdb.execute(SELECT status FROM tasks WHERE task_id? AND to_agent?,(task_id,to_agent)# ← 双条件).fetchone()它用task_id to_agent两个字段判断是否已存在。正常情况下没问题——但如果某次调用时to_agent传了空值或与原来不一致原记录: task_idDEV-016, to_agent小虾 ← 已存在 新调用: task_idDEV-016, to_agentNULL ← 双条件查不到to_agent不匹配 → 判定不存在 → 插入新记录to_agentNULL, created_atnow, 完成时间全空 → 任务被赝品覆盖 → 状态回到起点三个缺陷叠加才酿成穿越表没有主键允许重复记录存在性检查用双条件task_id to_agent——to_agent一变就失灵插入时 to_agent 偶尔传空某个调用链的隐藏bug修复存在性检查回归本质修复只需要一个判断上的改变# 修复前task_id to_agent 双条件to_agent一变→查不到→插入赝品existingSELECT...WHERE task_id? AND to_agent?# 修复后只看 task_id任务的唯一标识就是task_id——其他都不该参与判断existingSELECT...WHERE task_id?task_id 就是任务的身份证。判断任务是否已存在只看身份证就够了——to_agent 只是分配给谁不该影响这个任务存不存在的判断。顺带把建表语句也补上了主键正确的做法CREATETABLEtasks(task_idTEXTPRIMARYKEY,-- 补上主键——物理上杜绝重复记录...);三条教训第一没有主键的表迟早出灵异事件。主键不只是性能优化——它是数据完整性的地基。一张允许同ID多记录的表任何查重/覆盖逻辑都会失灵你以为查到了其实查错了行。所有业务表都必须有主键或唯一约束——这不是建议是底线。第二存在性检查要用唯一标识不用组合条件。判断这条记录存不存在应该用永远不变的那个字段task_id而不是task_id 另一个可能变化的字段to_agent。组合条件里任何一个字段出问题判断就失真。第三灵异重置先查数据本身。我们花了两天查代码状态机/时间戳/并发——全清白其实数据库里一眼就能看出端倪to_agent 为空的赝品记录。当代码逻辑查不出问题时去查数据——数据会告诉你真相124条异常记录摆在那比代码更诚实。结尾修复后任务不再穿越了——开发完成的就停在完成态测试通过的就停在通过态。我在建表注释里加了一行主键是表的身份证。没有身份证的表谁都能冒充你——包括一条看起来全新的旧任务。数据库没有主键就像房间没有门牌号——谁都能住进来谁也说不清谁是谁。完本文是多Agent派发系统系列第37篇。第36篇讲编译吃掉代码构建产物这一篇讲数据库吃掉状态无主键重复插入——都是数据与实际不符的灵异事件一个出在构建链一个出在数据完整性。