← 返回资讯

两个网关任务同时累计用量为何少一笔,怎样用数据库原子累加替代先读后写

2026-10-24

两个任务都先读到旧值,再把各自算出的新值写回,会丢掉一次累加。将累加放入数据库更新表达式,或在唯一桶键上使用UPSERT递增,可避免这种覆盖;事件去重仍需单独设计。

复现应用层读改写丢失

本例特意在读取阶段不保持读事务,确保展示的是应用先读后写的覆盖:两个任务都读到0,各自算出1,先后写回以后只有1。它不把SQLite读快照升级写事务时的busy错误混成同一种现象,排查时需要保留事务开始位置与实际错误。

确定聚合键与唯一约束

修正版让数据库执行n=usage.n+excluded.n,两次各加1,最终为2。唯一(tenant,bucket)确定两个任务更新同一个统计桶,excluded.n表示本次贡献,不是客户端根据旧总数算出的最终值。附件用两个连接和明确交错顺序,重现两种结果而无需高并发压测。

唯一桶键不仅避免两个统计行并存,也决定ON CONFLICT实际命中哪一行。若桶包含日期,日期口径应先归一,避免同一天写成不同字符串。修正版将本次增量交给数据库相加,客户端不再发送根据旧总数计算出的新总数;这才是避免覆盖的关键改变。

核心表达式(完整建表与输入见附件):

INSERT INTO usage VALUES('A','day',1)
ON CONFLICT(tenant,bucket) DO UPDATE
SET n=usage.n+excluded.n;

说明事件重放仍需独立去重

原子累加解决覆盖,不解决同一事件因重试被计入两遍。若请求重试、队列重投或补数会重复,应先按事件标识去重,再在同一合适事务内更新统计。整数样本也没有涉及金额舍入、退款或跨时区账期,所以不要直接拿它替换生产计费逻辑。

适用范围:合成整数请求数;不接入上游账务,UPSERT不等于幂等计费。

配套样本与复核入口

  • run.py:独立运行程序,包含合成输入、操作和正反例断言。
  • case.json:输入说明、预期任务与适用范围,便于保存和交接。
  • observed.json:本次实际执行输出;临时路径、进程号和计时可能随运行变化。

空目录运行即可得到lost与atomic。先确认两个读取都发生在首次写入之前,理解为什么两份计算都生成1,再看数据库表达式如何累计到2。不要把样例的顺序提交误读成并发性能测试;这里用确定交错证明覆盖问题,比依赖偶发线程时序更容易复核。实际队列多次投递仍需另添事件唯一键测试。

Python3.8以上及标准库;使用Python自带SQLite。程序不读取现有网关数据库,全部样本写入内存或新建临时目录,执行完自动清理;没有任何模型API调用。

python -B run.py

阅读输出时,先按case.json中的任务逐项核对网关用量SQL原子累加,再与observed.json比较。非零退出表示依赖或断言需要处理,应保留实际错误。

一手依据1(ON CONFLICT可按唯一冲突执行更新;此语法不负责事件去重。);一手依据2(SQLite只允许一个同时写事务;BEGIN/COMMIT/ROLLBACK控制合成表事务范围。)。

继续阅读:同一秒有两条调用,网关用量累计表为何一次跳过两行;请求计数器只加整数,为什么increase算出小数且不能拿来结账;预留 token 没用完,网关并发结束后怎样避免配额越扣越少。

加微信,把这一页发到你手机上

扫码加站长企业微信,会自动把这一页的链接发给你。模型接入、token 计费上有问题,也可以直接问。

去留微信

不想自己搭网关、自己维护渠道?

力达云是托管的 OpenAI / Anthropic 双格式端点,一期提供 DeepSeek,注册送 ¥5 额度。

去试用

这个页面有问题?

提交时会附带当前页面地址和浏览器信息,帮助我们定位问题。不填联系方式即为匿名。