Hook: Lock monitoring is the database version of checking the hallway outside a meeting room: the room may be fine, but the line outside tells you who is stuck and why.
Question: What is lock monitoring in SQL, and why does it matter for performance?
Answer: Lock monitoring means checking which sessions currently hold locks, which sessions are waiting, and which query is causing the block. In SQL databases, locks protect data from conflicting changes, but too many or too-long locks can make other queries stall. Good monitoring helps you find the blocker fast, distinguish a normal wait from a deadlock, and fix the real cause instead of guessing.
Interview-Ready Answer: I use lock monitoring to answer three things quickly: who is holding the lock, who is waiting, and what object is involved. In PostgreSQL, for example, I would join pg_locks to pg_stat_activity and look for granted = false or wait_event_type = 'Lock'. That lets me spot blocking chains, set a timeout if needed, and tell whether the problem is a long transaction, a missing index causing a large update, or an actual deadlock.
Detailed Explanation: A lock is a rule that says, 'this row, table, or object is being used in a way that other sessions must respect.' Lock monitoring is not about changing locks; it is about observing the traffic jam they create. In performance work, this matters because a query can be slow for two very different reasons: it is doing too much work, or it is waiting behind another session.
pg_locks shows the locks and pg_stat_activity shows the running sessions and their current wait event.granted = true means the lock is already held; false means the session is stuck waiting.| Setting | What it limits | Default |
|---|---|---|
lock_timeout | Time spent waiting for a lock | 0 off |
statement_timeout | Total time for the statement | 0 off |
deadlock_timeout | Delay before deadlock check | 1s |
The important detail is that lock_timeout is not the same as statement_timeout. One says 'do not wait too long for another session'; the other says 'do not let the whole query run too long.' Also, deadlock_timeout is not a failure limit by itself; it is the point where the database decides to check for a deadlock.
Lock monitoring is usually cheap because it reads metadata about current sessions, not user table data. Think of it as roughly linear in the number of active locks and sessions, not the size of the table. In a busy OLTP app, dozens or even hundreds of active locks can be normal, especially during short bursts of write traffic. The real danger is not a high lock count by itself; it is a long transaction that holds locks for seconds or minutes.
One useful version note: in PostgreSQL 9.6 and later, pg_blocking_pids() and wait_event_type make blocker lookup much easier. Older versions often require a more manual self-join on pg_locks. Across engines the idea is the same, but the view names differ: MySQL uses Performance Schema lock tables, and SQL Server uses DMVs.
Memory rule: do not ask only 'how many locks are there?'; ask 'who is waiting, who is blocking, and for how long?' That is the signal that matters.
Real-World Example: Imagine a checkout service in an e-commerce app. A nightly inventory reconciliation job updates thousands of products in one big transaction. It looks harmless because CPU stays low, but it holds row locks for a long time. During peak traffic, customer checkout requests try to update the same product rows and start waiting behind that job.
What you see in production is not always a crash. More often, p95 latency jumps from 120 ms to 6 seconds, the API logs show statements waiting on locks, and the app pool gets clogged with requests that are not doing useful work. In PostgreSQL, a monitoring query would show one session with an old query_start and many sessions with wait_event_type = 'Lock'. The fix is usually not 'add more servers'; it is to shorten the transaction, batch the job, and make sure the write path uses the right index so it touches fewer rows and holds locks for less time.
What goes wrong: if the team misunderstands the symptom, they may raise the connection pool, which makes the queue look bigger and can amplify the outage. Users see spinning checkout pages, customer support sees duplicate retries, and the database shows a single blocker session that stayed idle in transaction after fetching data and before committing.
-- PostgreSQL demo: inspect locks held by the current session and safely report blockers.
-- This script is self-contained and leaves no permanent data behind.
DROP TABLE IF EXISTS lock_demo;
CREATE TEMP TABLE lock_demo (
id integer PRIMARY KEY,
qty integer NOT NULL
);
INSERT INTO lock_demo (id, qty)
VALUES (1, 10),
(2, 20);
-- Keep the transaction open so the row lock remains visible in pg_locks.
BEGIN;
UPDATE lock_demo
SET qty = qty - 1
WHERE id = 1;
-- Show the locks for this backend. The important part is seeing what is held right now.
SELECT
l.locktype,
l.mode,
l.granted,
COALESCE(c.relname, '(no relation)') AS relation,
a.state,
a.wait_event_type,
a.wait_event
FROM pg_locks AS l
JOIN pg_stat_activity AS a
ON a.pid = l.pid
LEFT JOIN pg_class AS c
ON c.oid = l.relation
WHERE a.pid = pg_backend_pid()
ORDER BY l.granted DESC, l.locktype, l.mode;
-- Blocker/waiter summary.
-- In a real production incident, this is the query you would run when requests are hanging.
-- If no other session is waiting, the query returns a friendly message instead of a blank result.
SELECT COALESCE(
(
SELECT string_agg(
format('waiter %s blocks on blocker %s', wa.pid, bl.pid),
CHR(10)
)
FROM pg_stat_activity AS wa
JOIN LATERAL unnest(pg_blocking_pids(wa.pid)) AS b(blocker_pid)
ON TRUE
JOIN pg_stat_activity AS bl
ON bl.pid = b.blocker_pid
WHERE wa.wait_event_type = 'Lock'
),
'No lock waits right now'
) AS lock_wait_summary;
-- Production safeguard: fail fast instead of waiting forever.
SET lock_timeout = '2s';
-- This succeeds in a single-session demo.
-- In a second session, if another transaction were holding a conflicting lock,
-- PostgreSQL would cancel this statement after 2 seconds.
UPDATE lock_demo
SET qty = qty - 1
WHERE id = 2;
ROLLBACK;Follow-up & Tricky Questions:
pg_blocking_pids() for a quick answer and confirm it with pg_locks plus pg_stat_activity. That gives the blocker, the waiter, and the SQL text behind each session.pg_locks and pg_stat_activity; MySQL uses Performance Schema lock tables; SQL Server uses DMVs like sys.dm_tran_locks and sys.dm_exec_requests. The exact names change, but the mental model is always 'who holds, who waits, what object.'lock_timeout? Use it for user-facing requests when waiting is worse than failing fast, such as web APIs or checkout flows. That way you can retry or return a clear error instead of letting the request sit forever.SELECT block writers? No. Normal reads in PostgreSQL use MVCC, so they usually do not block writers; a locking read like SELECT ... FOR UPDATE is different because it intentionally takes row locks.COMMIT release every lock? Transaction-level locks are released at commit or rollback, but session-level advisory locks can stay until you explicitly release them or the session ends. That is a common gotcha in long-lived connections.statement_timeout the same as lock timeout? No. statement_timeout limits total runtime; lock_timeout limits how long you are willing to wait for a lock. They solve different problems and are often used together.Tricky / gotcha questions:
Common Mistakes:
wait_event_type, lock views, and query plans together so you know whether the delay is contention or execution work.Memory Hook: Think: Who sits, who waits, who blocks the door? If you can answer those three questions, you have found the lock problem.
Cheat Sheet:
pg_locks + pg_stat_activity.granted = false or wait_event_type = 'Lock' to spot waiting sessions.lock_timeout controls waiting for a lock; statement_timeout controls total runtime.Practice Tasks:
lock_timeout to a small value and test how your app or script behaves when a lock is unavailable.