In this lesson, you will learn one of the most important realities of SQL Server performance:
many “slow database” incidents are not caused by CPU or disk—they are caused by waiting.
That waiting usually comes from locks and blocking when many users run at the same time.
Big Idea: Concurrency tuning is a balance between correctness and speed.
SQL Server uses locks to protect data correctness. Your job is to reduce unnecessary blocking without allowing bad data.
The best DBAs do not “remove locks.” They design transactions, indexes, queries, and isolation levels to work well under load.
120–180 minutes (concepts + demos + lab)
Locks are internal mechanisms that protect data consistency.
SQL Server uses locks so multiple users can work at the same time without corrupting data.
Blocking happens when one session holds a lock and another session needs a conflicting lock,
so the second session must wait. Blocking is normal sometimes, but excessive blocking causes timeouts and “system slowness.”
A deadlock happens when two (or more) sessions block each other in a cycle.
SQL Server detects this and kills one session (the “deadlock victim”) to break the cycle.
Deadlocks are not “slow.” They are failures.
Key teaching point: Blocking is waiting for a lock. Deadlock is a cycle that SQL Server must break by killing a session.
SQL Server can lock different things: rows/keys, pages, tables, and metadata. It also uses different lock modes.
You do not need to memorize every mode today, but you must understand the big ones and the idea of “conflicts.”
Key teaching point: S and X locks conflict. That conflict is the common root of blocking.
Lock duration is strongly controlled by transaction scope. The longer the transaction, the longer locks are held.
Many production blocking issues come from transactions that are longer than the business truly requires.
Key teaching point: The fastest concurrency fix is often not “add hardware.”
It is “shorten transactions” and “reduce the rows touched.”
Isolation levels control how much one transaction can “see” of another transaction and how much blocking occurs.
Concurrency is not just about locks; it is also about the isolation strategy.
Key teaching point: Versioning isolation (RCSI/SNAPSHOT) can reduce blocking,
but it shifts load to TempDB and requires monitoring and planning.
The correct approach to blocking is simple:
find the root blocker and understand why it is holding locks so long.
Do not waste time tuning the blocked queries first.
/* Blocking overview */
SELECT
r.session_id,
r.blocking_session_id,
r.status,
r.wait_type,
r.wait_time,
r.wait_resource,
r.total_elapsed_time,
DB_NAME(r.database_id) AS database_name,
SUBSTRING(t.text, (r.statement_start_offset/2)+1,
CASE WHEN r.statement_end_offset = -1 THEN LEN(CONVERT(nvarchar(max), t.text))
ELSE (r.statement_end_offset - r.statement_start_offset)/2 + 1
END) AS statement_text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.blocking_session_id <> 0
ORDER BY r.wait_time DESC;
How to interpret: The blocking_session_id is the blocker. If many sessions are blocked by the same blocker,
that blocker is your primary incident target.
/* Replace @blocker_session_id with the session_id you found */ DECLARE @blocker_session_id int = 0; -- TODO: set this SELECT s.session_id, s.login_name, s.host_name, s.program_name, s.status, s.open_transaction_count, r.command, r.cpu_time, r.total_elapsed_time, r.logical_reads, r.reads, r.writes, r.wait_type, r.wait_time, r.wait_resource, DB_NAME(r.database_id) AS database_name, LEFT(t.text, 4000) AS running_batch_text FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_requests r ON s.session_id = r.session_id OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE s.session_id = @blocker_session_id;
How to interpret: If open_transaction_count is high or the transaction has been open for a long time,
the real fix may be application logic, batching, or transaction design—not “add an index.”
/* Sessions waiting on locks (LCK waits) */ SELECT r.session_id, r.status, r.blocking_session_id, r.wait_type, r.wait_time, r.wait_resource, r.total_elapsed_time, DB_NAME(r.database_id) AS database_name, LEFT(t.text, 4000) AS query_text FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.wait_type LIKE 'LCK%' ORDER BY r.wait_time DESC;
How to interpret: Many sessions with LCK waits usually means a concurrency bottleneck.
The next step is always: find and analyze the blocker.
Deadlocks happen when two sessions each hold locks the other needs, creating a cycle.
SQL Server chooses one session to kill and returns an error to the application.
Key teaching point: Deadlocks are fixed by changing design:
consistent access order, shorter transactions, better indexing, and reduced lock footprint.
Key teaching point: The best concurrency tuning is not hacks.
It is good design: minimal work per transaction, good indexes, and predictable access patterns.
Students will identify a blocker, determine why it is blocking, and propose a realistic fix.
Prompt:
“A reporting query blocks order entry every morning at 9:00 AM. What are your top 3 hypotheses?
What evidence would you collect first to confirm which one is true?”
In the next lesson, we will cover In-Memory OLTP in SQL Server.
You will learn what memory-optimized tables are, which workloads benefit, how natively compiled procedures work,
and how to decide whether In-Memory OLTP is the right performance solution—or the wrong one.
Not a member yet? Register now
Are you a member? Login now