Concurrency Control Problems in Database Systems
Concurrency control is the set of techniques a DBMS uses to coordinate simultaneous transactions while preserving correctness. Without control, interleaved reads and writes can produce anomalies even when every individual transaction is correct.
This section explains three fundamental problems:
- Lost update — one transaction overwrites another transaction’s change.
- Dirty read — a transaction reads data written by another transaction before it commits.
- Unrepeatable read — a transaction reads the same row twice and obtains different committed values.
These problems are consequences of insufficient isolation between transactions. The SQL standard defines isolation levels that trade consistency for concurrency and performance.
A useful correctness goal is serializability: although transactions may execute concurrently, the final result should be equivalent to running them one at a time.
Footnotes
-
Transaction Isolation Levels (ODBC) - Microsoft Learn - Describes the four SQL-92 isolation levels and the read phenomena they may permit. ↩
Transaction Isolation Levels and Concurrency Anomalies
Transactions and schedules
Suppose two transactions, and , access the same data item .
- means that reads .
- means that writes .
- means that commits.
- means that aborts or rolls back.
A schedule describes the actual execution order. A serial schedule completes one transaction before starting another:
A non-serial schedule interleaves operations:
Non-serial execution can improve throughput, but the DBMS must ensure that the interleaving does not violate application rules or database constraints.
Key terms
- Atomicity
- Consistency
- Commit
- Rollback
Footnotes
-
Chapter 18: Concurrency Control - Database System Concepts - Academic material covering schedules, serializability, locking, validation, and multiversion control. ↩
How to Analyze a Concurrent Schedule
- 1Step 1
Label each operation according to its transaction, such as and .
- 2Step 2
Record which data items each transaction reads or modifies. Conflicts occur when operations from different transactions access the same item and at least one operation is a write.
- 3Step 3
Determine whether a transaction has committed before another transaction reads or depends on its changes.
- 4Step 4
If one transaction reads the same row twice, compare the values. A changed value indicates a possible unrepeatable read.
- 5Step 5
If two transactions calculate updates from the same old value, inspect whether the later write overwrites the earlier one. This indicates a possible lost update.
- 6Step 6
Use an appropriate isolation level, row lock, version check, or serializable transaction to prevent the anomaly.
1. Lost Update
A lost update occurs when two transactions read the same original value, independently calculate new values, and then write those values back. The later write replaces the earlier write, so one logical update disappears.
Example
Assume an account balance is initially .
| Time | Transaction | Transaction | Balance |
|---|---|---|---|
| 1 | Reads balance | — | |
| 2 | — | Reads balance | |
| 3 | Calculates | — | |
| 4 | — | Calculates | |
| 5 | Writes | — | |
| 6 | — | Writes |
The correct result, assuming both operations should be applied, is:
However, the final value is . The deposit made by has been lost.
Schedule
The problem is not merely that the writes overlap. Each transaction computed its result from a stale copy of the balance.
Common causes
- Read-modify-write logic performed in separate application steps.
- Missing row-level locks.
- An isolation level or storage engine that permits conflicting writes.
- Updating an entire row from an old object representation.
- Omitting a version or timestamp condition in an
UPDATEstatement.
Prevention
A database can prevent lost updates by:
- Locking the row before reading it for modification.
- Performing the calculation inside an atomic SQL update.
- Using optimistic concurrency control with a version column.
- Using a sufficiently strong isolation level.
- Detecting a conflict and retrying the transaction.
An atomic update is often safer than reading a value into application code:
Here, the database evaluates the expression as part of the update operation rather than allowing application code to overwrite a value read earlier.
Lost Update Timeline
- 1Step 1
Both transactions read .
- 2Step 2
computes , while computes .
- 3Step 3
writes .
- 4Step 4
writes , replacing the value produced by .
- 5Step 5
The final value reflects only 's calculation, so 's update is lost.
Lost Update Warning
A transaction can be individually valid yet still lose another transaction’s work. Do not assume that a successful write means the application update was safely merged.
2. Dirty Read
A dirty read occurs when reads a value changed by , but later rolls back. Consequently, has used data that never became part of the committed database state.
Example
Assume an account balance is initially .
| Time | Transaction | Transaction | Visible balance |
|---|---|---|---|
| 1 | Updates balance to | — | Uncommitted |
| 2 | — | Reads balance | |
| 3 | Rolls back | — |
Transaction read , but the committed balance is still . If uses the value to approve a purchase, generate a report, or make another update, its decision is based on data that never existed as committed state.
Schedule
The read is dirty because occurs before commits.
Why dirty reads are dangerous
Dirty reads can cause:
- Incorrect financial or inventory decisions.
- Reports that include temporary values.
- Cascading errors in dependent transactions.
- Inconsistent application behavior when a rollback occurs.
- Violations of business rules based on uncommitted state.
Prevention
Dirty reads are prevented when a transaction reads only committed data. Typical mechanisms include:
READ COMMITTEDisolation or stronger.- Shared locks that remain effective until the writer commits.
- MVCC snapshots that hide uncommitted versions.
- Avoiding database configurations equivalent to
READ UNCOMMITTED.
Under PostgreSQL, even the named READ UNCOMMITTED level does not expose dirty data; PostgreSQL treats it as READ COMMITTED.
Footnotes
-
Transaction Isolation Levels (ODBC) - Microsoft Learn - Defines dirty reads as reads of uncommitted data that may later be rolled back. ↩
-
PostgreSQL 18 Documentation: Transaction Isolation - Documents PostgreSQL isolation behavior, snapshots, and its implementation-specific guarantees. ↩
3. Unrepeatable Read
An unrepeatable read occurs when a transaction reads a row, another transaction updates or deletes it and commits, and the first transaction reads the row again.
Example
Assume an employee’s salary is initially .
| Time | Transaction | Transaction | Salary |
|---|---|---|---|
| 1 | Reads salary | — | |
| 2 | — | Updates salary to | |
| 3 | — | Commits | |
| 4 | Reads salary again | — |
Within one logical transaction, observed two different committed values for the same row.
Schedule
Unlike a dirty read, the second value is committed. The problem is that the database state changed between two reads performed by the same transaction.
Consequences
Unrepeatable reads can cause:
- A report whose calculations use two different versions of the same row.
- Incorrect comparisons between an initial and final value.
- Decisions based on a value that changes during validation.
- Confusing user experiences in multi-step workflows.
Prevention
Unrepeatable reads can be prevented by:
REPEATABLE READisolation or stronger.- Holding read locks until transaction completion.
- Reading from a consistent MVCC snapshot.
- Explicitly locking rows with a database-specific
FOR UPDATEorFOR SHAREclause when appropriate.
At READ COMMITTED, each statement may see a new committed snapshot, so repeated statements can observe different values. PostgreSQL documents this behavior explicitly.
Difference from a phantom read
An unrepeatable read concerns an existing row whose value changes or whose row disappears. A phantom read concerns the set of rows matching a predicate.
Example:
- Unrepeatable read:
SELECT salary FROM employees WHERE employee_id = 7returns 65,000. - Phantom read:
SELECT * FROM employees WHERE department = 'Sales'returns 10 rows and then 11 because another transaction inserted a matching employee.
Footnotes
-
Transaction Isolation Levels (ODBC) - Microsoft Learn - Defines nonrepeatable reads as repeated reads of a row returning different data after another transaction commits an update or delete. ↩
-
PostgreSQL 18 Documentation: Transaction Isolation - Documents PostgreSQL isolation behavior, snapshots, and its implementation-specific guarantees. ↩
Isolation Levels and Common Read Phenomena
The SQL-92 model describes which read anomalies may occur at each isolation level.
Isolation-level comparison
| Isolation level | Dirty read | Unrepeatable read | Phantom read | Typical trade-off |
|---|---|---|---|---|
READ UNCOMMITTED | Possible | Possible | Possible | Highest concurrency, weakest read consistency |
READ COMMITTED | Prevented | Possible | Possible | Common balance between consistency and throughput |
REPEATABLE READ | Prevented | Prevented | May be possible under the SQL model | Stable row reads, potentially more contention |
SERIALIZABLE | Prevented | Prevented | Prevented | Strongest correctness, possible blocking or retries |
This table reflects the conventional SQL isolation model. Specific DBMS implementations can provide stronger behavior or use different mechanisms. For example, PostgreSQL reports that dirty reads are not possible, and its REPEATABLE READ implementation also prevents phantom reads even though the SQL standard permits them at that level.
Isolation levels do not automatically solve every business rule. A transaction can avoid dirty and unrepeatable reads while still experiencing a serialization anomaly, write skew, or a lost-update pattern caused by application logic. The required guarantee must therefore be matched to the operation.
Footnotes
-
PostgreSQL 18 Documentation: Transaction Isolation - Documents PostgreSQL isolation behavior, snapshots, and its implementation-specific guarantees. ↩
Selecting a Concurrency-Control Strategy
- 1Step 1
Determine whether the transaction only reads, performs a read-modify-write, enforces a constraint, or scans a range of rows.
- 2Step 2
For committed-only reads, use at least
READ COMMITTED. For stable repeated row reads, considerREPEATABLE READ. For cross-row business invariants, considerSERIALIZABLE. - 3Step 3
Pessimistic control acquires locks before conflicts occur. Optimistic control allows work to proceed and validates versions or conflicts before commit.
- 4Step 4
Short transactions reduce lock duration, waiting, deadlocks, and the probability of conflicts.
- 5Step 5
A deadlock victim, serialization failure, or optimistic version conflict should be rolled back and safely retried when the operation is retryable.
- 6Step 6
Use concurrent test sessions to reproduce schedules involving reads, writes, commits, and rollbacks.
Concurrency-Control Techniques
Lock-based control
A lock controls who may read or write an item.
- A shared lock generally permits multiple readers.
- An exclusive lock generally permits one writer and prevents conflicting access.
- A transaction may wait when another transaction holds an incompatible lock.
Two-phase locking has a growing phase, during which locks are acquired, and a shrinking phase, during which locks are released. Strict variants retain write locks until commit or rollback, helping prevent cascading effects and dirty reads.
MVCC
MVCC allows readers to access an appropriate committed version while a writer creates a newer version. This can reduce reader-writer blocking, but the exact guarantees depend on the DBMS and isolation level.
Optimistic concurrency control
Optimistic concurrency control is useful when conflicts are relatively rare.
A version-column pattern is:
1SELECT balance, version 2FROM accounts 3WHERE account_id = 1; 4 5UPDATE accounts 6SET balance = 120, 7 version = version + 1 8WHERE account_id = 1 9 AND version = 4;
If the update affects zero rows, another transaction changed the record first. The application must reload the row, reconcile the change, or retry.
Atomic database operations
Whenever possible, express a modification as one database operation:
1UPDATE inventory 2SET quantity = quantity - 1 3WHERE product_id = 42 4 AND quantity > 0;
The application should verify the affected-row count. An affected-row count of zero may indicate insufficient inventory or a concurrent conflict.
Footnotes
-
CMU 15-445/645 Lecture Notes: Two-Phase Locking - Explains two-phase locking, lock compatibility, serializability, and deadlock detection. ↩
-
Chapter 18: Concurrency Control - Database System Concepts - Covers multiversion schemes and the use of old data versions to increase concurrency. ↩
Practical Design Rule
Prefer atomic SQL updates for counters, balances, and stock quantities. If a value must be read before it is written, use a row lock or a version check.
Frequently Asked Questions
Concurrency Control Flashcards
Worked Comparison
Consider the following three schedules involving an item .
Schedule A: Lost update
Both transactions read . The final value is , so the update producing is lost.
Schedule B: Dirty read
Transaction reads before commits. Since aborts, was never committed.
Schedule C: Unrepeatable read
Transaction reads twice and obtains different values because committed an update between the reads.
Diagnostic questions
When examining a schedule, ask:
- Did one transaction overwrite another transaction’s calculated result?
- Did a read occur before the writer committed?
- Did the same transaction read the same row twice after another transaction committed a modification?
- Is the problem about a row’s value, or about the membership of a query result set?
- Which isolation level or conflict-detection mechanism supplies the required guarantee?
Isolation Is Not a Substitute for Transaction Design
Choosing an isolation level is necessary but not sufficient. Correct transaction boundaries, atomic statements, appropriate locks, constraint checks, and retry handling must work together.
Knowledge Check
Which situation describes a lost update?
Explore Related Topics
Thrashing in Operating Systems: Causes, Mechanisms, and Control
Thrashing is a severe performance collapse where the operating system spends most of its time handling page faults and swapping pages because the combined working‑set demand of active processes exceeds available physical memory.
- When and (total demand > frames), paging dominates execution.
- The root cause is memory overcommitment—excessive degree of multiprogramming or processes with too large footprints.
- Symptoms include very high page‑fault rates, intense disk paging activity, and sharply reduced CPU productivity.
- Classic controls are the working‑set model, page‑fault‑frequency monitoring, lowering multiprogramming, using local replacement, and adding RAM.
- Preventive rule: only keep a set of active processes whose working sets can collectively fit in RAM.
Data Warehouse Systems vs. Operational Database Systems: A Comprehensive Comparison
Deadlock Prevention by Breaking Coffman Conditions
Deadlock can be avoided by breaking any one of the four Coffman conditions through system‑level policies.
- Mutual exclusion: Make resources sharable where feasible (e.g., spooling), though many devices are inherently exclusive.
- Hold and wait: Require a process to request all needed resources before it starts, eliminating partial holding.
- No preemption: Allow the OS to force a process to release its current resources when a new request cannot be satisfied.
- Circular wait: Impose a total order on resource types and permit requests only in increasing order, preventing cycles such as .