Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 43

Database Locking and Deadlocks

Recommended resources

Hello! Welcome back to our deep dive into the internals of backend systems.

In our previous lessons, we covered how databases ensure atomicity, consistency, and isolation through ACID properties and transaction isolation levels. We also saw how the query optimizer chooses between different join algorithms to execute queries efficiently. A common thread is the challenge of managing concurrency: how does a database allow many operations to happen at once without corrupting data?

Today, we will explore the primary mechanism that makes this possible. Our learning outcome is to explain database locking mechanisms (row-level, table-level) and how deadlocks occur. We'll go from the fundamental purpose of locks to the different levels at which they operate, and then investigate the classic problem of deadlocks—what they are, how databases resolve them, and how you, as a developer, can prevent them.

Why Do We Need Locks?

As we've discussed, when multiple transactions access the same data concurrently, several issues can arise, such as Dirty Reads, Lost Updates, and Non-Repeatable Reads. Locks are the database's fundamental tool for implementing transaction isolation and preventing these anomalies.

A lock is essentially a marker that a transaction places on a piece of data (a resource). This marker signals to other transactions that the resource is in use and enforces rules about how they can interact with it, thereby ensuring data integrity.

To start, let's get a clear overview of these concepts.

Locking Mechanisms in Relational Database Management Systems

This article from mainri.ca provides a concise introduction to the purpose of locks and the problems they solve. It also gives a high-level overview of lock types and granularity, which we will explore in detail.

Please read the introduction and the 'Types of Locks' section. Focus on understanding the concurrency problems mentioned and the basic definitions of Shared and Exclusive locks.


Lock Granularity: The Concurrency vs. Overhead Trade-off

A database doesn't just have one type of lock; it can apply locks at different levels of "granularity." This choice represents a critical trade-off between concurrency and overhead.

Locking Mechanisms in Relational Database Management Systems

Let's continue with the same article to understand the different levels at which locks can be applied.

Read the section titled 'Lock Granularity'. Pay attention to the descriptions of Row-Level, Page-Level, and Table-Level locks and the trade-offs mentioned for each.

To summarize the key points on granularity:

  • Table-Level Lock:

    • What it is: Locks an entire table.
    • Concurrency: Very low. If one transaction locks a table, no other transaction can operate on it.
    • Overhead: Very low. The database only needs to manage one lock for the whole table.
    • Use Case: Ideal for operations that affect the entire table structure, like ALTER TABLE.
  • Row-Level Lock:

    • What it is: Locks a single row.
    • Concurrency: Very high. Different transactions can modify different rows in the same table simultaneously.
    • Overhead: High. If a transaction updates thousands of rows, the database must acquire and manage thousands of individual locks, which consumes memory and CPU.
    • Use Case: The default for most OLTP (Online Transaction Processing) workloads, like updating a specific user's profile or processing a single order.
  • Page-Level Lock:

    • What it is: Locks a data page, which is a fixed-size block of storage on disk that contains multiple rows.
    • Concurrency & Overhead: A compromise between table and row-level locks.
    • Note: This is often an internal implementation detail of the database engine itself, rather than something you directly control.

The fundamental tension is that fine-grained locks (like row-level) allow for more parallelism but come with higher management costs. Coarse-grained locks (like table-level) are cheap to manage but severely limit concurrency.


Types of Locks and How They Interact

Now that we understand where locks can be placed, let's look at what they do. The two most fundamental types are:

  1. Shared (S) Lock: Acquired for read operations. Multiple transactions can hold a shared lock on the same resource simultaneously. Think of it as a "readers' lock."
  2. Exclusive (X) Lock: Acquired for write operations (UPDATE, DELETE, INSERT). Only one transaction can hold an exclusive lock on a resource at any given time. An exclusive lock is incompatible with all other locks, including shared locks. Think of it as a "writer's lock."

This relationship is often shown in a lock compatibility matrix:

Requested -> Shared (S) Exclusive (X)
Held: Shared (S) Yes No
Held: Exclusive (X) No No

Modern databases like PostgreSQL have a much more nuanced and optimized set of locks. Since your goal is to go to the deepest level possible, we'll dive into the specific lock modes used by PostgreSQL. This will give you a concrete understanding of how a real-world database manages this complexity.

All Postgres Locks Explained | A Deep Dive

This video from Hussein Nasser is an excellent deep dive into PostgreSQL's locking system. It connects abstract concepts to the specific, named lock modes that are used implicitly by database commands.

This is a dense but highly valuable video. Please watch the following sections carefully: 02:22 - 07:07: Introduction to locks, the core concepts of shared vs. exclusive, and an overview of PostgreSQL's lock categories. 08:59 - 28:14: A detailed exploration of table-level locks. Don't worry about memorizing every lock mode. The goal is to understand that different operations (like SELECT vs. VACUUM vs. TRUNCATE) acquire different 'intention' locks on the table and how the conflict matrix determines what can run concurrently. 28:14 - 39:15: A look at row-level locks, including the explicit FOR UPDATE and FOR SHARE clauses and how they conflict.

The key insight from the video is that even a simple SELECT or UPDATE involves acquiring locks at multiple levels (e.g., a table-level "intention" lock and a row-level lock). The database uses this complex system to maximize concurrency while guaranteeing correctness.

9 Types of Database Locks
This diagram summarizes various lock types found in databases, including their granularity (row, page, table) and purpose (shared, exclusive, update). It serves as a great visual reference for the concepts we've just discussed.

The Inevitable Problem: Deadlocks

While locks are essential for preventing data corruption, they introduce a new problem: deadlocks. A deadlock occurs when two or more transactions are stuck in a circular wait, each waiting for a resource that the other holds.

Database Deadlock Scenario
This sequence diagram illustrates a classic deadlock scenario. Alice locks a row in `post_details` and Bob locks a row in `post`. The deadlock occurs when Alice then tries to lock the row Bob holds, while Bob simultaneously tries to lock the row Alice holds. Neither can proceed.

How Deadlocks Occur

Let's formalize the example from the diagram:

  1. Transaction A starts and acquires an exclusive lock on row 1.
  2. Transaction B starts and acquires an exclusive lock on row 2.
  3. Transaction A now attempts to acquire an exclusive lock on row 2, but it's held by Transaction B. So, Transaction A waits.
  4. Transaction B now attempts to acquire an exclusive lock on row 1, but it's held by Transaction A. So, Transaction B waits.

Now, A is waiting for B, and B is waiting for A. This is a circular dependency, and neither transaction can ever complete on its own.

Deadlock Detection and Resolution

Databases are designed to handle this. They periodically run a deadlock detection algorithm, which typically involves building a wait-for graph. In this graph, nodes are transactions, and a directed edge from A to B means A is waiting for a lock held by B. A cycle in this graph indicates a deadlock.

Once a deadlock is detected, the database must resolve it by choosing one transaction as the victim. It aborts the victim transaction, which rolls back all its changes and releases all its locks. This breaks the cycle, allowing the other transaction(s) to proceed. The application connected to the victim transaction receives an error and typically must retry the entire transaction.

Why Do Databases Deadlock and How Do They Resolve It

This article by Arpit Bhayani provides an excellent, structured explanation of deadlocks, from their causes to how databases detect and resolve them.

Read the sections 'Database Deadlocks', 'Why Deadlocks Occur', 'Deadlock Detection', 'Deadlock Resolution Strategies', and 'Database-Specific Deadlock Handling'. This will solidify your understanding of the theory and database behavior.

Deadlock Prevention: The Developer's Responsibility

While the database can resolve deadlocks, they are disruptive and hurt performance. The best approach is to write application code that avoids them in the first place.

This video provides a fantastic, practical demonstration of creating a deadlock and then applying two key prevention strategies.

Understanding and Visualizing Database Deadlocks: A Practical SQL Guide

Let's see a deadlock in action and, more importantly, how to prevent it. This video provides a hands-on demonstration in PostgreSQL.

Watch the following sections to see a live demo: 01:52 - 06:57: Watch how a deadlock is intentionally created using two concurrent transactions updating rows in a different order. 07:21 - 09:49: This demonstrates the most critical prevention strategy: always acquiring locks in a consistent order. 09:49 - 12:27: This shows a second strategy: pessimistic locking using SELECT ... FOR UPDATE to acquire all necessary locks upfront.

The two most effective strategies for preventing deadlocks are:

  1. Consistent Lock Ordering: This is the golden rule. Ensure that all parts of your application that modify multiple resources do so in the same, predefined order. For example, always update the users table before the orders table. This prevents circular waits from ever forming.
  2. Keep Transactions Short: The longer a transaction holds locks, the higher the probability of conflict. Do expensive computations or external API calls outside of the database transaction. The transaction itself should be as quick as possible: begin, read/write, commit.

Conclusion

In this lesson, we've pulled back the curtain on one of the most critical aspects of database concurrency control.

Key Takeaways:

  • Locks are for Isolation: They are the primary mechanism databases use to prevent concurrent transactions from interfering with each other and corrupting data.
  • Granularity is a Trade-off: Locks can be applied at the table, page, or row level. The choice balances concurrency (favoring fine-grained locks like row-level) against management overhead (favoring coarse-grained locks like table-level).
  • Lock Types Define Behavior: The most basic lock modes are Shared (read) and Exclusive (write), but real-world databases like PostgreSQL use a more sophisticated system of lock modes to maximize performance.
  • Deadlocks are Circular Waits: They occur when two or more transactions are stuck, each waiting for a lock held by the other.
  • Databases Detect and Resolve Deadlocks: Using a wait-for graph, a database can identify deadlocks and resolve them by aborting a "victim" transaction.
  • Prevention is Key: As a developer, you can prevent most deadlocks by enforcing a consistent lock order across your application and keeping transactions short and focused.

Preview of the Next Lesson

We've seen how transactions use locks for isolation. But what about the 'D' in ACID—Durability? How does a database guarantee that once a transaction is committed, the changes are permanent, even if the server crashes immediately after? In our next lesson, we will explore the mechanism that makes this possible: Write-Ahead Logging (WAL).