By WebNest Studio Engineering Team
Database and SQL Tutorial
Database Locking Concepts
Locking is the mechanism a database uses to control concurrent access to the same data, preventing two transactions from making conflicting changes at the same time. When a transaction needs to read or write a row, it may need to acquire a lock on that row first, and other transactions that need a conflicting lock on the same row must wait until it is released.
Locking is closely related to isolation levels — stricter isolation is typically implemented, in part, by holding locks for longer or acquiring them more broadly.
Why it matters
Locks are how a database stops two transactions from overwriting each other. They also cause the hardest production problems: queries that hang while waiting for a lock, and deadlocks in which two transactions wait for each other for ever. Understanding them is what lets you diagnose a system that "sometimes just freezes".
Shared Locks vs. Exclusive Locks
A shared lock (also called a read lock) allows a transaction to read a row, and multiple transactions can hold shared locks on the same row at the same time — reading doesn't conflict with other reading. An exclusive lock (also called a write lock) is required to modify a row (UPDATE, DELETE), and only one transaction can hold an exclusive lock on a given row at a time; it also blocks any other transaction from acquiring a shared lock on that row until it is released. This is why a long-running write can block readers, depending on the isolation level and locking strategy in use.
- Shared (S) lock — many transactions can hold it simultaneously; used for reads.
- Exclusive (X) lock — only one transaction can hold it; used for writes; blocks both shared and exclusive locks from others.
Deadlocks
A deadlock occurs when two (or more) transactions each hold a lock the other one needs, and neither can proceed. For example, Transaction A locks Row 1 and then tries to lock Row 2, while Transaction B has already locked Row 2 and is trying to lock Row 1 — each is waiting on the other forever. Databases detect this situation automatically and resolve it by choosing one transaction as the "deadlock victim," forcibly rolling it back with an error so the other can proceed. Applications should catch this error and retry the failed transaction.
Lock Timeouts
Rather than waiting forever for a lock to become available, most databases support a lock timeout: if a transaction cannot acquire a needed lock within a configured period, it fails with a timeout error instead of blocking indefinitely. This protects the system from a single stuck or slow transaction freezing every other transaction that needs the same data. Applications typically handle a lock timeout the same way as a deadlock: catch it and retry, often with a short backoff delay.
Examples
A classic deadlock between two transactions
-- Transaction A
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE account_id = 1;
-- ... then tries:
UPDATE accounts SET balance = balance + 50 WHERE account_id = 2;
-- Transaction B (running concurrently)
BEGIN;
UPDATE accounts SET balance = balance - 20 WHERE account_id = 2;
-- ... then tries:
UPDATE accounts SET balance = balance + 20 WHERE account_id = 1;
ERROR: deadlock detected
DETAIL: Process A waits for ShareLock on account_id 2; blocked by Process B.
Process B waits for ShareLock on account_id 1; blocked by Process A.
HINT: One transaction was automatically rolled back; the application should retry it.
Setting a lock timeout to fail fast instead of blocking indefinitely
BEGIN;
SET LOCAL lock_timeout = '3s';
UPDATE inventory SET quantity = quantity - 1
WHERE product_id = 900;
COMMIT;
ERROR: canceling statement due to lock timeout
(If the row was locked by another transaction for more than 3 seconds, this UPDATE fails immediately instead of waiting indefinitely, and the application can retry.)
Common Mistakes
- Always locking rows in a different order across different parts of the application, which is the most common cause of avoidable deadlocks.
- Treating a deadlock error as a fatal bug instead of building retry logic around it — deadlocks are an expected, normal occurrence under concurrent load and are meant to be retried.
- Holding a transaction open (and its locks) for far longer than necessary, such as waiting on user input or an external API call mid-transaction, which blocks other transactions needlessly.
- Confusing row-level locks with table-level locks — some operations (like certain schema changes) escalate to locking an entire table, which is far more disruptive than a single row lock.
Exercise
This exercise needs a real database server, so it cannot run in the playground. Write a PostgreSQL transaction that transfers 100 from account 1 to account 2 safely under concurrency: lock both rows first with a single SELECT ... FOR UPDATE that takes them in id order, then make the two updates and commit.
Expected result: two sessions running this transaction at once cannot deadlock, because both request the row locks in the same order.
BEGIN;
-- TODO: lock the rows for accounts 1 and 2, in id order
-- TODO: subtract 100 from account 1
-- TODO: add 100 to account 2
COMMIT;
Show hints
SELECT ... FOR UPDATEtakes an exclusive lock on each row it returns, held until the transaction ends.- Adding
ORDER BY idmakes every session lock the rows in the same order.
Show solution
BEGIN;
SELECT id, balance
FROM accounts
WHERE id IN (1, 2)
ORDER BY id
FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
Quick quiz
1. What is a deadlock?
- A. A query that returns no rows
- B. Two transactions each waiting for a lock that the other holds
- C. A table with no index
- D. A transaction that takes a long time
Show answer
B. Neither can continue, so the database detects the cycle and aborts one of them.
2. How do a shared lock and an exclusive lock differ?
- A. Many transactions can hold a shared lock on the same data at once; an exclusive lock can be held by only one
- B. A shared lock is for writing
- C. An exclusive lock allows other writers
- D. There is no difference
Show answer
A. Shared locks are taken for reading and exclusive locks for writing.
3. Which practice reduces deadlocks?
- A. Using longer transactions
- B. Removing all indexes
- C. Accessing tables and rows in the same order in every transaction
- D. Disabling transactions
Show answer
C. If everyone acquires locks in one order, a waiting cycle cannot form.
Interview questions
What is a deadlock and how can it be prevented or handled?
Show answer
A deadlock occurs when two or more transactions each hold a lock that another needs, so none can proceed. The database detects this and aborts one transaction with an error. Deadlocks are made rare by acquiring locks in a consistent order, keeping transactions short, and touching as few rows as possible. Since they cannot be eliminated entirely, the application should catch the error and retry the transaction.
What is the difference between optimistic and pessimistic locking?
Show answer
Pessimistic locking takes a lock when the data is read, for example with SELECT ... FOR UPDATE, so that nobody else can change it until the transaction ends; it suits data with frequent conflicts. Optimistic locking takes no lock: each row has a version number, and the update includes WHERE version = ?; if no row is updated, someone else changed it first and the operation is retried. It suits data where conflicts are rare.
Key Points to Remember
- Shared locks allow concurrent reads; exclusive locks are needed for writes and block all other access to that row.
- A deadlock happens when transactions wait on each other in a cycle; the database detects it and rolls back one transaction automatically.
- Lock timeouts prevent a transaction from blocking forever waiting on a lock, failing fast instead so the application can retry.
- Keeping transactions short and locking rows in a consistent order across the application reduces both deadlocks and lock contention.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs your SQL on SQLite in your browser. Standard SQL works; a few features that exist only in PostgreSQL or MySQL do not. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a project environment on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering