Karya Semi
HomeBlogSearchCategoriesAboutContact
Karya Semi

Less noise. More notes.

HomeBlogAboutContactPrivacy PolicyDisclaimer

© 2026 Karya Semi. All rights reserved.

XGitHubLinkedIn
  1. Home
  2. /Categories
  3. /Software Engineering

Understanding Database Locks: Row, Page, and Table Level Mechanics

A practical breakdown of database locking mechanisms, isolation levels, and lock escalation to prevent deadlocks and performance degradation in production.

Dian Rijal Asyrof/July 20, 2026/5 min read
Illustration for Understanding Database Locks: Row, Page, and Table Level Mechanics

Database locking forms the foundation of data integrity in multi-user transactional applications. Under low concurrent traffic, relational database management systems manage locks automatically without developer intervention. However, as query throughput increases and concurrent update operations scale, poor lock strategy manifests as sudden latency spikes, query timeouts, and cascading deadlocks.

Understanding how storage engines acquire, maintain, and release locks across structural granularities is essential for maintaining backend system stability.

The Hierarchy of Lock Granularity

Databases manage concurrency by restricting access to data at different resource levels. The granularity of a lock dictates the balance between concurrent read/write throughput and memory overhead.

1. Row-Level Locks

Row locks represent the finest lock granularity. Only the specific record being updated or locked (SELECT ... FOR UPDATE) is blocked. Concurrent application workers can freely modify adjacent rows within the same table page without contention.

-- Explicit row lock for transactional account updates
BEGIN;
SELECT id, balance 
FROM user_balances 
WHERE account_id = 9042 
FOR UPDATE;
 
UPDATE user_balances 
SET balance = balance - 250.00 
WHERE account_id = 9042;
COMMIT;

2. Page-Level Locks

Page locks secure an entire memory block or disk storage page (typically 8KB in PostgreSQL). Storage engines frequently utilize page locks during B-tree index maintenance, page splits, or specialized multi-row updates.

3. Table-Level Locks

Table locks block write or read operations across an entire table. While explicit table locks can be requested via LOCK TABLE, they are more commonly triggered implicitly by schema migration queries (ALTER TABLE), unindexed foreign key updates, or lock escalation events.

Shared vs. Exclusive Locks: The Core Modes

Behind every lock granularity level, storage engines categorize locks into fundamental modes that determine compatibility between concurrent transactions:

  • Shared Locks (S): Acquired during read operations (SELECT). Multiple transactions can hold shared locks on the same resource simultaneously.
  • Exclusive Locks (X): Acquired during write operations (INSERT, UPDATE, DELETE). Only one transaction can hold an exclusive lock on a resource at a time, blocking both reads and writes from other transactions.
  • Intent Locks (IS / IX): Applied at higher levels (like table or page levels) to indicate that a transaction holds or intends to acquire fine-grained locks lower down the hierarchy. Intent locks prevent higher-level operations from taking exclusive table locks while individual row updates are active.
Existing Lock / Requested LockShared (S)Exclusive (X)Intent Shared (IS)Intent Exclusive (IX)
Shared (S)CompatibleConflictCompatibleConflict
Exclusive (X)ConflictConflictConflictConflict
Intent Shared (IS)CompatibleConflictCompatibleCompatible
Intent Exclusive (IX)ConflictConflictCompatibleCompatible

Understanding Lock Escalation

Lock escalation occurs when a storage engine converts thousands of fine-grained locks (such as individual row locks) into a single coarse lock (such as a table lock) to free up memory used by internal lock tracking tables.

When lock escalation fires in high-throughput production systems, unexpected table locks can freeze concurrent API endpoints. To prevent uncontrolled lock escalation:

  • Batch Bulk Updates: Break massive UPDATE or DELETE operations modifying 100,000+ rows into smaller chunked transactions of 1,000 records.
  • Index Foreign Keys: Ensure foreign key columns have explicit indexes so updates don't trigger full table verification scans.
  • Minimize Transaction Lifespans: Avoid executing external HTTP calls or heavy disk I/O operations while keeping a database transaction open.

Deadlocks and Resolution Strategies

A deadlock occurs when two or more transactions hold locks on resources that the other transactions require to proceed. For example, Transaction A holds a lock on Row 1 and requests Row 2, while Transaction B holds a lock on Row 2 and requests Row 1. Neither transaction can proceed, creating a circular dependency.

Modern relational engines run deadlock detection threads that periodically analyze lock dependency graphs. When a circular wait condition is detected, the database engine aborts one of the participating transactions (the victim) with a serialization failure error, allowing the remaining transaction to complete.

To minimize deadlock occurrences in production backend services:

  1. Consistent Lock Ordering: Always access and update multiple tables or rows in the exact same deterministic order across all application endpoints.
  2. Set Query Timeouts: Configure explicit statement timeouts (statement_timeout in PostgreSQL) so queries don't hang indefinitely waiting for locked resources.
  3. Handle Retries Gracefully: Implement automatic application-level retries with exponential backoff for transactions aborted due to deadlock resolution.

MVCC and Lock-Free Reads in PostgreSQL

Modern databases like PostgreSQL minimize read/write contention through Multi-Version Concurrency Control (MVCC). Under MVCC, write operations create a new tuple version of a row rather than overwriting existing data in place.

Because older tuple versions remain visible to concurrent transactions based on snapshot visibility rules, SELECT queries do not acquire shared locks on data rows. Readers never block writers, and writers never block readers.

However, MVCC introduces maintenance overhead:

  • Table Bloat: Stale tuple versions accumulate until cleaned up by VACUUM background processes.
  • MVT Overhead: Heavy update workloads generate high write-ahead log (WAL) volume and index updates for new tuple pointers.

Lock Contention in High-Throughput Payment Systems

To see these mechanics in practice, consider a high-volume payment processing system updating merchant account balances. If 500 API request workers attempt to update the same single merchant balance row simultaneously, a classic row lock queue forms:

[ Worker Thread 1 ] -> Acquired Row Lock (Updating Balance)
[ Worker Thread 2 ] -> Waiting in Lock Queue (Blocked)
[ Worker Thread 3 ] -> Waiting in Lock Queue (Blocked)
...
[ Worker Thread 500] -> Connection Timeout (504 Gateway Error)

Even though the database acquires fine-grained row locks, severe lock contention on a single row degrades application throughput to single-threaded speeds. To resolve row contention bottlenecks:

  • Ledger-Based Accounting: Instead of updating a single row balance directly (UPDATE balances SET amount = amount + X), append transaction records to an insert-only ledger table (INSERT INTO transaction_ledger ...). Calculate balances asynchronously or via periodic aggregation.
  • Partitioned Counters: Split a single hot balance counter into N sub-counter rows (e.g., balance_shard_1 through balance_shard_10) and randomly pick a shard row for updates to distribute locking load.

Transaction Isolation Levels and Concurrency

The transaction isolation level configured on a connection controls how aggressively the database applies locks and snapshot visibility rules:

  • Read Committed: Holds write locks until the transaction completes, but creates a fresh snapshot per SQL statement execution.
  • Repeatable Read: Guarantees that data read during a transaction remains unchanged by enforcing a single transaction-level snapshot.
  • Serializable: Emulates strict serial transaction execution, relying on predicate locks (SIREAD locks in Postgres) to eliminate phantom reads and serialization anomalies.

Practical Diagnostic Queries for PostgreSQL Production Incidents

When database CPU spikes or connection pools exhaust during an operational incident, identifying lock contention immediately is critical. The following query retrieves active blocking locks in PostgreSQL:

SELECT
    blocked_locks.pid AS blocked_pid,
    blocked_activity.usename AS blocked_user,
    blocking_locks.pid AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query AS blocked_statement,
    blocking_activity.query AS current_statement_in_blocking_process
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks 
    ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
    AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
    AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
    AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
    AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
    AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;

Running targeted lock diagnostic queries allows engineering teams to identify long-running uncommitted transactions and resolve blocking bottlenecks before cascading service failures occur.

DR

Dian Rijal Asyrof

Writes about useful AI tools, programming practice, and the craft of building reliable software.

Previous articleHow to Evaluate a RAG Application With a Regression Test SetNext articleEngineering for the Agentic Era: Infrastructure, Identity, and Operational Control
DatabaseBackendPostgresqlPerformance
On this page↓
  1. The Hierarchy of Lock Granularity
  2. 1. Row-Level Locks
  3. 2. Page-Level Locks
  4. 3. Table-Level Locks
  5. Shared vs. Exclusive Locks: The Core Modes
  6. Understanding Lock Escalation
  7. Deadlocks and Resolution Strategies
  8. MVCC and Lock-Free Reads in PostgreSQL
  9. Lock Contention in High-Throughput Payment Systems
  10. Transaction Isolation Levels and Concurrency
  11. Practical Diagnostic Queries for PostgreSQL Production Incidents

On this page

  1. The Hierarchy of Lock Granularity
  2. 1. Row-Level Locks
  3. 2. Page-Level Locks
  4. 3. Table-Level Locks
  5. Shared vs. Exclusive Locks: The Core Modes
  6. Understanding Lock Escalation
  7. Deadlocks and Resolution Strategies
  8. MVCC and Lock-Free Reads in PostgreSQL
  9. Lock Contention in High-Throughput Payment Systems
  10. Transaction Isolation Levels and Concurrency
  11. Practical Diagnostic Queries for PostgreSQL Production Incidents

See also

Illustration for Postgres Transactions Are a Hidden Superpower for Distributed Systems
Software Engineering/Jul 3, 2026

Postgres Transactions Are a Hidden Superpower for Distributed Systems

Before reaching for Kafka, Redis, or a dedicated message queue, consider what Postgres can already do. The database most teams already use has surprisingly powerful coordination primitives built in.

3 min read
PostgresDatabase
Illustration for How to Design a Reliable Webhook Receiver
Web Development/Jul 20, 2026

How to Design a Reliable Webhook Receiver

A practical guide to webhook receivers: signature checks, idempotency, retries, ordering, persistence, observability, and tests.

5 min read
WebhooksBackend