How to prevent race conditions when updating a user’s balance in a high‑concurrency environment? #196085
-
🏷️ Discussion TypeQuestion 💬 Feature/Topic AreaCode Search and Navigation BodyWe have a REST endpoint that adds or subtracts from a user’s balance (e.g., /api/users/{id}/balance). Under heavy load, multiple concurrent requests for the same user sometimes produce incorrect final balances (lost updates). We’re using PostgreSQL and a single application instance for now. What are the best strategies to ensure atomic updates without locking the entire table? Code examples would help. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
You’re facing a classic lost update problem. Here are three reliable solutions, ordered from simplest to most flexible
UPDATE users
SET balance = balance + 20, version = version + 1
WHERE id = ? AND version = ?;If the row count is 0, retry the whole operation (read new balance, recalculate, update).
BEGIN;
SELECT balance FROM users WHERE id = ? FOR UPDATE;
-- calculate new balance in application
UPDATE users SET balance = ? WHERE id = ?;
COMMIT;PostgreSQL will queue concurrent requests for the same row. Simple and safe
UPDATE users
SET balance = balance + ?
WHERE id = ? AND balance + ? >= 0; -- optional constraintThis is lock‑free and fastest. No race conditions because the update is a single statement |
Beta Was this translation helpful? Give feedback.
You’re facing a classic lost update problem. Here are three reliable solutions, ordered from simplest to most flexible
Add an integer version column to the users table. When updating, check that the version hasn’t changed:
If the row count is 0, retry the whole operation (read new balance, recalculate, update).
Works well when conflicts are rare. No explicit locks, good for read‑heavy workloads
Wrap the operation in a transaction and lock only the affected row: