gotchasqlCriticalpending
Race Condition Patterns and Prevention in Web Applications
Viewed 0 times
race conditionconcurrencylost updateoptimistic lockingidempotencydouble submit
Problem
Multiple concurrent requests modify the same data, causing lost updates, duplicate records, or inconsistent state (double charges, overselling inventory).
Solution
Common race conditions and fixes:
1. Lost update (read-modify-write)
2. Double submission
3. Overselling (check-then-act)
4. Unique constraint race
1. Lost update (read-modify-write)
-- WRONG: two requests read balance=100, both subtract 50
SELECT balance FROM accounts WHERE id = 1; -- 100
UPDATE accounts SET balance = 50 WHERE id = 1; -- Both set to 50!
-- FIX: atomic update
UPDATE accounts SET balance = balance - 50 WHERE id = 1 AND balance >= 50;
-- Check affected rows - if 0, insufficient balance2. Double submission
// FIX: idempotency key
app.post('/api/charge', async (req, res) => {
const { idempotencyKey, amount } = req.body;
// Check if already processed
const existing = await db.charges.findUnique({
where: { idempotencyKey }
});
if (existing) return res.json(existing); // Return previous result
const charge = await processPayment(amount);
await db.charges.create({
data: { idempotencyKey, ...charge }
});
return res.json(charge);
});3. Overselling (check-then-act)
-- FIX: optimistic locking
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND stock > 0 AND version = 5;
-- If affected rows = 0, retry with fresh data
-- FIX: SELECT FOR UPDATE (pessimistic locking)
BEGIN;
SELECT * FROM products WHERE id = 1 FOR UPDATE;
-- Other transactions wait here
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;4. Unique constraint race
-- FIX: use INSERT ON CONFLICT
INSERT INTO users (email, name)
VALUES ('alice@example.com', 'Alice')
ON CONFLICT (email) DO NOTHING;Why
Race conditions are intermittent and hard to reproduce. They only appear under concurrent load, making them easy to miss in development and devastating in production.
Gotchas
- ORMs often do read-modify-write under the hood - check the generated SQL
- Optimistic locking is better for low-contention, pessimistic for high-contention
Context
Handling concurrent data modifications
Revisions (0)
No revisions yet.