Locking, Deadlocks & High-Concurrency Patterns
Master row locks, table locks, deadlock prevention, and optimistic locking for scalable applications.
๐ฏ What You'll Learn
- PostgreSQL's lock hierarchy from ACCESS SHARE to ACCESS EXCLUSIVE
- Row-level locking: FOR UPDATE, FOR SHARE, SKIP LOCKED, NOWAIT
- What causes deadlocks and how to prevent them
- Monitoring and detecting lock contention in production
- Optimistic locking with version numbers for web applications
๐ Lock Types โ The Hierarchy
Think of locks like building access levels: some let everyone in (ACCESS SHARE for reads), while others clear the entire building (ACCESS EXCLUSIVE for schema changes). Understanding which operations take which locks prevents surprises in production.
Lock Types
PostgreSQL's lock hierarchy and monitoring
-- PostgreSQL Lock Types (from weakest to strongest)
-- 1. ACCESS SHARE โ acquired by SELECT
-- Doesn't block anything except ACCESS EXCLUSIVE
SELECT * FROM employees; -- takes ACCESS SHARE lock
-- 2. ROW SHARE โ acquired by SELECT ... FOR UPDATE/SHARE
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- Locks this specific row so no one else can modify it
-- 3. ROW EXCLUSIVE โ acquired by INSERT, UPDATE, DELETE
UPDATE employees SET salary = 90000 WHERE id = 5;
-- Locks only the affected rows,
...๐ Row-Level Locking
Row locks are the surgical tools of concurrency โ they lock only the rows you need, leaving the rest of the table accessible. SKIP LOCKED is a game-changer for job queues.
๐ก Pro Tip โ SKIP LOCKED for Job Queues
FOR UPDATE SKIP LOCKED turns a regular table into a high-performance job queue. Multiple workers grab different unlocked rows simultaneously โ no waiting, no contention, no external queue system needed.
Row Locking Strategies
FOR UPDATE, FOR SHARE, SKIP LOCKED, and NOWAIT
-- Row-level locking strategies
-- FOR UPDATE: exclusive lock (I will modify this row)
BEGIN;
SELECT * FROM inventory
WHERE product_id = 100
FOR UPDATE;
-- Now this row is locked until COMMIT/ROLLBACK
-- Other sessions trying FOR UPDATE will WAIT
UPDATE inventory SET quantity = quantity - 1
WHERE product_id = 100;
COMMIT;
-- FOR SHARE: shared lock (I'm reading, don't change it)
BEGIN;
SELECT * FROM products
WHERE id = 100
FOR SHARE;
-- Other FOR SHARE allowed, but FOR UPDATE blocked
COMMIT;
-
...โ ๏ธ Deadlocks โ The Deadly Embrace
A deadlock is like two people in a narrow hallway โ each waiting for the other to move. The database detects this and kills one transaction. Prevention is simple: always lock resources in the same order.
โ ๏ธ Common Mistake
Locking rows in different orders across different code paths. If function A locks row 1 then row 2, and function B locks row 2 then row 1 โ deadlock. Always sort IDs before locking.
Deadlock Prevention
Why deadlocks happen and how to prevent them
-- DEADLOCK: two transactions each waiting for the other
-- Session A: -- Session B:
-- BEGIN; -- BEGIN;
-- UPDATE accounts -- UPDATE accounts
-- SET bal = bal - 100 -- SET bal = bal - 50
-- WHERE id = 1; โ locks 1 -- WHERE id = 2; โ locks 2
--
-- UPDATE accounts -- UPDATE accounts
-- SET bal = bal + 100 -- SET bal = bal + 50
-- WHERE id = 2; โ WAITS -- WHERE id = 1; โ WAITS
--
...๐ Detecting Lock Issues
In production, lock contention shows up as slow queries and timeouts. These diagnostic queries help you find the culprit.
Lock Monitoring
Find blocked queries and long-running transactions
-- Detecting and monitoring lock issues
-- 1. Find blocked queries right now
SELECT blocked.pid AS blocked_pid,
blocked.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking.query AS blocking_query,
NOW() - blocked.query_start AS wait_duration
FROM pg_stat_activity blocked
JOIN pg_locks bl ON blocked.pid = bl.pid AND NOT bl.granted
JOIN pg_locks gl ON bl.relation = gl.relation
AND bl.locktype = gl.locktype AND gl.granted
JOIN pg_stat_activity blocking ON
...๐ฏ Optimistic Locking
For web applications where users edit data over minutes (not milliseconds), holding database locks is impractical. Optimistic locking uses a version number to detect conflicts at save time.
Optimistic Locking Pattern
Version-based conflict detection for web apps
-- Optimistic Locking: no database locks, use version numbers
-- Best for: web apps where lock duration is unpredictable
-- Table has a version column
CREATE TABLE documents (
id INT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
version INT DEFAULT 1,
updated_at TIMESTAMP DEFAULT NOW()
);
-- Step 1: Read the document (no lock!)
SELECT id, title, content, version
FROM documents WHERE id = 42;
-- Returns: version = 5
-- Step 2: User edits in the browser (minutes pass...)
-
...๐ Quick Reference
| Strategy | Use When |
|---|---|
| FOR UPDATE | Short transactions, immediate writes |
| SKIP LOCKED | Job queues, parallel workers |
| NOWAIT | Fail-fast instead of waiting |
| Optimistic lock | Web apps, long user sessions |
| Consistent order | Deadlock prevention |
๐ Lesson Complete!
You now understand locking mechanics, deadlock prevention, and optimistic concurrency. Next, learn about table partitioning for massive datasets!
Sign up for free to track which lessons you've completed and get learning reminders.