In databases
Solved with transactions at the appropriate isolation level, or SELECT ... FOR UPDATE — locking the row until the end of the transaction.
In caches
Atomic Redis operations (INCR, SETNX, Lua scripts) prevent race conditions at the cache level.
Optimistic vs pessimistic locking
- Optimistic — on write, check whether data has changed since reading (version number / CAS). No locks, but retries possible
- Pessimistic — lock the resource on read so no one else can change it. No retries, but reduces concurrency