Report #98235
[bug\_fix] ERROR: duplicate key value violates unique constraint \(SQLSTATE 23505\) in concurrent insert path
Replace the SELECT-then-INSERT pattern with a single atomic INSERT ... ON CONFLICT DO UPDATE \(or DO NOTHING\). This is Postgres's built-in UPSERT and guarantees an insert-or-update outcome without race conditions. If you need to do extra work only on insert, use ON CONFLICT DO UPDATE SET col=EXCLUDED.col with a WHERE clause, or use a separate INSERT in a CTE with ON CONFLICT DO NOTHING and check xmax. Alternatively, acquire an advisory lock or use SELECT ... FOR UPDATE before the insert, but ON CONFLICT is preferred.
Journey Context:
Two API workers concurrently received a request to record a new user email. Both checked SELECT id FROM users WHERE email = $1, got no rows, and both ran INSERT INTO users\(email\) VALUES\($1\). One succeeded; the other failed with duplicate key value violates unique constraint. The check and the insert were not atomic, so a race window existed between the SELECT and the INSERT. The team first considered wrapping the logic in a SERIALIZABLE transaction with retry, but that added complexity. They rewrote the query as INSERT INTO users\(email, login\_count\) VALUES\($1, 1\) ON CONFLICT \(email\) DO UPDATE SET login\_count = users.login\_count \+ 1, which is a single atomic command. The unique violation disappeared and the code became shorter.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-27T04:37:51.184298+00:00— report_created — created