fix: replace CTE with inline UPDATE to prevent lost update on period reset

CTE snapshot in READ COMMITTED can cause concurrent requests during
period reset to lose one increment. Inline CASE in single UPDATE
ensures PostgreSQL re-evaluates all expressions with latest row data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
erio
2026-03-06 20:07:02 +08:00
co-authored by Claude Opus 4.6
parent 9430776393
commit 465a58a1c9
+7 -11
View File
@@ -1697,27 +1697,23 @@ func (r *accountRepository) IncrementQuotaUsed(ctx context.Context, id int64, am
COALESCE((extra->>'quota_limit')::numeric, 0)`,
amount, id)
} else {
// 周期制:CTE 原子检查过期 → 重置 → 递增
// 周期制:单条 UPDATE 内联过期判断,避免 CTE snapshot 在 READ COMMITTED 下的 lost update
rows, err = r.sql.QueryContext(ctx,
`WITH reset_check AS (
SELECT id, extra,
CASE WHEN COALESCE((extra->>'quota_period_start')::timestamptz, '1970-01-01'::timestamptz)
+ $3::interval <= NOW()
THEN true ELSE false END AS needs_reset
FROM accounts WHERE id = $2 AND deleted_at IS NULL
)
UPDATE accounts SET extra = jsonb_set(
`UPDATE accounts SET extra = jsonb_set(
jsonb_set(
COALESCE(extra, '{}'::jsonb),
'{quota_used}',
to_jsonb(
CASE WHEN (SELECT needs_reset FROM reset_check) THEN $1
CASE WHEN COALESCE((extra->>'quota_period_start')::timestamptz, '1970-01-01'::timestamptz)
+ $3::interval <= NOW()
THEN $1
ELSE COALESCE((extra->>'quota_used')::numeric, 0) + $1 END
)
),
'{quota_period_start}',
to_jsonb(
CASE WHEN (SELECT needs_reset FROM reset_check)
CASE WHEN COALESCE((extra->>'quota_period_start')::timestamptz, '1970-01-01'::timestamptz)
+ $3::interval <= NOW()
THEN to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')
ELSE COALESCE(extra->>'quota_period_start', to_char(NOW() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')) END
)