Skip to content

upsert 每次「合并到既有行」都烧一个自增号,并覆写该行已有的业务号(健康计数器上实测,与陈旧无关) #7011

Description

@os-zhuang

一句话说明

SqlDriver.upsert()成功路径上无界烧号:每一次「合并到既有行」都会先取一个新的自增号,然后把它写进那一行,覆盖掉该行原有的业务标识符。与计数器陈旧无关 —— 在完全健康的计数器上就能复现。

#6943(PR #6999)的范围审计中实测发现,刻意未顺手修(PD #10):修它需要一个语义裁决,不是那张卡的裁向。

实测(健康计数器,单行,全程只有 1 行)

create                        → CASE-00001     last_value 1
upsert 同一个 id(第一次)      → CASE-00002     last_value 2
upsert 同一个 id(第二次)      → CASE-00003     last_value 3

表里始终只有一行,而它的 case_number 被改写了两次。

成因

fillAutoNumberFields 在语句知道自己会 insert 还是 merge 之前就已经跑完了 —— 它发生在构建 ON CONFLICT … DO UPDATE 之前。而自增号列在 mergeColumns 里,所以合并分支会把这个新取的号一并写入。

两个后果,第二个更重:

  1. 无界烧号:每次 upsert 消耗一个号,即使一行也没新增。这与 Autonumber counter neither syncs to MAX(existing) per tenant nor re-checks on collision — warm-DB creates 409 in bursts, each failure burning a number (25 retries observed) #5495 / bulkCreate / upsert 仍会逐次烧号:#5495 的重播种只落在 create(),批次语义是独立决策 #6943 处理的「陈旧导致的一次性风暴」不是同一件事 —— 那些是有界的、每库一次的;这个是每次调用都发生,永不停止。
  2. 改写业务标识符:自增号通常是对外可见的单据号(CASE-00001)。一次以更新为目的的 upsert 会静默更换它,而调用方并没有要求改号。留空洞是一回事,改写已发出的标识符是另一回事。

需要裁决的问题(不预设答案)

这正是当初把 #6943#5495 拆出来的那一类语义问题,请分诊/维护者定夺:

  • 更新型 upsert 应当保留原有自增号吗? 直觉上应当 —— 号是那一行的身份。
  • 它应当在合并路径上取号吗? 若不应当,那么取号必须推迟到「确定要 insert」之后,或者在合并分支上把该列从 mergeColumns 里排除。
  • 既有数据怎么办? 已经被改写过的行无法从驱动侧还原。

边界(与相邻卡的关系)

关联

未认领,仅作记录,定级交分诊。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions