Concurrency control ensures that concurrent transactions produce correct results. The main techniques: locking (pessimistic: block conflicts before they happen), optimistic concurrency control (validate at commit time), and MVCC (each transaction sees a consistent snapshot).
Shared and exclusive locks
A shared lock (S-lock, read lock) allows concurrent reads. An exclusive lock (X-lock, write lock) blocks all other access. Multiple readers can hold S-locks simultaneously, but an X-lock is exclusive: no other lock (S or X) can coexist with it.
Scheme
; Lock manager simulation
(define locks (list)) ; list of (item . lock-type)
(define (can-grant? item lock-type)
(let ((held (filter (lambda (l) (string=? (car l) item)) locks)))
(cond
((null? held) #t) ; no lock held
((string=? lock-type "S")
(not (member "X" (map cdr held)))) ; S ok if no X held
(else#f)))) ; X needs no locks at all
(define (acquire item lock-type txn)
(if (can-grant? item lock-type)
(begin
(set! locks (cons (cons item lock-type) locks))
(display txn) (display " acquired ") (display lock-type)
(display " on ") (display item) (newline))
(begin
(display txn) (display " BLOCKED on ") (display item) (newline))))
(acquire "A""S""T1") ; granted
(acquire "A""S""T2") ; granted (shared ok)
(acquire "A""X""T3") ; blocked (X conflicts with S)
(newline)
(set! locks (list)) ; release all
(acquire "B""X""T1") ; granted
(acquire "B""S""T2") ; blocked (S conflicts with X)
Two-phase locking (2PL)
Two-phase locking guarantees serializability. Phase 1 (growing): acquire locks, never release. Phase 2 (shrinking): release locks, never acquire. Once a transaction releases any lock, it cannot acquire new ones. This prevents the interleaving patterns that cause anomalies.
Deadlock occurs when two transactions each hold a lock the other needs. Detection: build a wait-for graph. If it has a cycle, one transaction must be aborted (the victim). Prevention strategies include timeout and wound-wait/wait-die schemes.
Python
# Deadlock detection via wait-for graph cycle detectiondef detect_deadlock(waits_for):
"""waits_for: dict mapping txn -> set of txns it waits for"""def has_cycle(node, visited, stack):
visited.add(node)
stack.add(node)
for neighbor in waits_for.get(node, set()):
if neighbor notin visited:
if has_cycle(neighbor, visited, stack):
returnTrueelif neighbor in stack:
returnTrue
stack.discard(node)
returnFalse
visited = set()
for txn in waits_for:
if txn notin visited:
if has_cycle(txn, visited, set()):
returnTruereturnFalse# No deadlock: T1 waits for T2print("T1->T2:", detect_deadlock({"T1": {"T2"}}))
# Deadlock: T1 waits for T2, T2 waits for T1print("T1->T2->T1:", detect_deadlock({"T1": {"T2"}, "T2": {"T1"}}))
# Three-way deadlockprint("T1->T2->T3->T1:", detect_deadlock({
"T1": {"T2"}, "T2": {"T3"}, "T3": {"T1"}
}))
MVCC — multiversion concurrency control
MVCC lets readers and writers run concurrently without blocking each other. Each write creates a new version of the data. Readers see a consistent snapshot from when their transaction started. PostgreSQL, MySQL/InnoDB, and Oracle all use MVCC.
Scheme
; MVCC simulation: each write creates a new version.; Readers see the version valid at their start time.
(define versions (list)) ; list of (key timestamp value)
(define (mvcc-write key ts value)
(set! versions (cons (list key ts value) versions))
(display " Write ") (display key) (display "=") (display value)
(display " at t=") (display ts) (newline))
(define (mvcc-read key read-ts)
; Find latest version with timestamp <= read-ts
(let loop ((vs versions) (best #f))
(if (null? vs)
(if best (caddr best) #f)
(let ((v (car vs)))
(if (and (string=? (car v) key)
(<= (cadr v) read-ts)
(or (not best) (> (cadr v) (cadr best))))
(loop (cdr vs) v)
(loop (cdr vs) best))))))
(mvcc-write "X"1100)
(mvcc-write "X"3200)
(mvcc-write "X"5300)
(newline)
; T1 started at t=2, sees version from t=1
(display "T1 (start t=2) reads X = ")
(display (mvcc-read "X"2)) (newline)
; T2 started at t=4, sees version from t=3
(display "T2 (start t=4) reads X = ")
(display (mvcc-read "X"4)) (newline)
; T3 started at t=6, sees version from t=5
(display "T3 (start t=6) reads X = ")
(display (mvcc-read "X"6))
Neighbors
Cross-references
🖥 OS Ch.5 — deadlock: same concept, different domain (OS resources vs database locks)