Skip to content
实验报告
待周评
AI 报错了,为什么数据库反而多写了一条?
开源工程观察 · 实验研究

AI 报错了,为什么数据库反而多写了一条?

看到“提交失败”,再试一次就安全吗?我们让工具先写入、再报错,数据库里的记录揭开了一个容易被忽略的区别。

2026-09-15English →

AI 报错了,为什么数据库反而多写了一条?

如果页面提示“提交失败”,你会不会再点一次?

先想象一种情况:第一次提交其实已经保存成功,只是成功消息没有传回来。第二次点击,可能就会把同一件事再做一遍。

我们没有拿真实订单或付款做实验,而是用一个本地数据库,专门制造“已经写入,随后报错”的情况。结果比预想的还反常:外层只允许尝试一次,数据库里却多出了两条记录。

这个数字把问题带到了眼前:一次失败,究竟意味着没有做成,还是做成了却没能正确回话?

一次尝试,怎么会写两次?

线索来自 Ritiky23 为 CrewAI 提交的修复提案。CrewAI 是帮助多个 AI 助手调用工具、协作完成工作的框架。

我们也在开发协作系统,因此关心它怎样处理失败后的再次尝试。工具的回应可以是错误,但它对文件、数据库或外部服务造成的变化,可能已经发生。这个来源提供了具体代码,可以直接数一数动作究竟发生了几次。

旧代码把“整理输入参数”和“执行工具”放进了同一段错误处理。如果这段代码出了问题,后备处理就会换用原始参数,再调用一次工具。

于是出现了错位:本来想照顾“参数没整理好”的情况,却也接住了“工具已经写入,随后才报错”的情况。后者不需要再整理参数,却也被调用了第二次。

候选修复把两步分开:整理参数出错时仍可后备处理,真正执行工具时的错误,不再触发这一次额外调用。

把尝试次数和写入次数放在一起看

我们让一个测试工具先向 SQLite 数据库写入并保存一条记录,然后故意报错。SQLite 可以把数据库保存在本地文件里,便于直接核对结果。

保留项目原始调用方法,给它同样的输入,前后得到这组结果:

场景原代码:调用次数 / 保存记录数候选修复:调用次数 / 保存记录数
保存后报错,最多尝试一次2 / 21 / 1
保存后报错,最多尝试三次6 / 63 / 3
正常成功,最多尝试三次1 / 11 / 1
读取参数说明失败,但工具随后成功1 / 11 / 1

我们分别检查了等待工具返回、以及通过异步方式等待结果的两条实现,结果一致。

第一行说明,修复去掉了一次尝试里多出来的那次调用。第二行又提醒我们:允许尝试三次,修复后仍可能保存三条记录。

已经保存的记录,不会因为随后报错自动消失

图 1:保存记录与返回结果是两步。来源:本轮 crew.json 中的 SQLite 记录;六次减少到三次,并不意味着事情只做了一遍。

修复确实有效,但它解决的是一个具体的重复调用问题。要让一件业务即使被反复请求也只生效一次,还需要识别“这些请求其实是同一件事”,并能找到之前的结果。

那就先查一查?还要看查到了什么

顺着这个结果,我们继续问:重新执行以前,先查询记录,是否就足够了?

另一个管理 AI 会话的应用 Orca 提供了相邻线索。brennanb2025 在这份修复提案中处理的是:恢复会话时,历史里没找到一条消息,能不能就认定它没送达?

我们给相关程序相同的空历史输入。原代码会判断“未送达”,候选代码则保留“还不能确定”。在另一组输入里,候选也不再根据恢复时拿到的拒绝记录,要求换一个新消息编号。

原因不难理解:查到“没有”之前,得先知道自己查的记录是否足以证明没有。没找到成功记录,可能是没有成功,也可能是这份记录本来就不完整。

这里测的是程序如何解释输入,没有真的重发消息。它与前面的数据库实验共同提醒我们:报错和没查到记录,都不能单独充当“放心再做一次”的理由。

不让它盲目重做,也不能让人一直等

保留“还不能确定”更诚实,却带来了新的使用问题:如果系统一直不知道结果,后面的工作怎么办?

这是本轮实验之后最值得继续研究的地方。下一步可以模拟:动作保存成功,回复丢失,程序随后重启。恢复以后,能否沿用同一个操作编号找到已有结果,而不是再写一条?如果找不到,怎样让用户核实或决定下一步?

这些场景还没有在本轮完整跑过,不能提前宣布“已经解决”。

对于开发者,需要回答的是:谁保存操作编号和最终结果,重启后还能不能查到?对于使用者,如果页面说失败、刷新后却发现已经成功,你希望它提供“查询这次提交”的入口,还是只留下一个“再试一次”按钮?

两项实验分别验证了什么

CrewAI 对照固定为 a328710 与 2ab3821。从原始源码提取完整 use/ause/_use/_ause 四个方法,保留控制流;工具选择、遥测、缓存和格式化等周边依赖使用替身。SQLite 文件写入和提交实际发生。四场景、同步/异步两路径、两个版本,共16条观察。没有运行完整 CrewAI 系统,也没有真实邮件、订单或付款。

Orca 对照固定为 742a7ad 与 e6f789b,加载完整生产模块及实际导入。五类消息与历史输入、五类结果处置输入,在两个版本共形成20条观察;两类输入组没有串成真实发送链。空历史测试同时设定边界一致且没有正在进行的轮次。带恢复标记的拒绝保持未确认,不请求新身份。

还有一个保留的限制:未带恢复标记、原因为 not_delivered 的合成拒绝输入,候选仍请求新身份;本轮未证明真实发送端会产生这个组合。原 PR 中关于拒绝原因限制、手机端和编排端的评审意见,也未由我们复现。保留未知后,等待能否最终解除同样尚未验证。

完整来源、输入、结果及复跑方法。这些数据不代表线上事故频率,也未证明 CodeFlowMu 存在对应缺陷。

Last updated: