SlideShare a Scribd company logo
Blocking, Locking, and Deadlocking
www.BrentOzar.com
Help@BrentOzar.com
Glossary
Locking: Larry takes out a lock.
Blocking: Sarah needs a lock, but Larry has it.
SQL Server will let Sarah wait for forever,
and the symptom is LCK* waits.
Deadlocks:
Larry has locks, but needs some held by Sarah.
Sarah has locks, but needs some held by Larry.
SQL Server solves this one by killing somebody,
and the symptom is dead bodies everywhere.
3 ways to fix blocking & deadlocks
1. Have enough indexes to make your queries fast,
but not so many that they slow down DUIs,
making them hold more locks for longer times.
2. Use the right isolation level for your app’s needs.
3. Identify queries involved in blocking & deadlocks
with sp_BlitzLock, then tune them.
The database we’ll use
Open source, licensed with Creative Commons
Full 100+GB version and mini 10GB version
Learn more: BrentOzar.com/go/querystack
Let’s query the Users table.
You only have the clustered index on ID,
the white pages of the table.
How many accessed the system on my birthday?
Let’s reward them.
You only have the clustered index on ID,
the white pages of the table.
What’s the execution plan for this query:
BEGIN TRAN
UPDATE dbo.Users
SET Reputation = Reputation + 100
WHERE LastAccessDate >= '2013/11/10'
AND LastAccessDate <= '2013/11/11'
Your execution plan:
1. Shuffle through all of the pages,
examining the LastAccessDate.
2. If it matches our filter, lock this row and update it.
3. Keep moving through the table.
4. After we finish, hold the locks until we commit.
SQL Server’s execution plan
It does show the clustered index scan,
but doesn’t show our locks.
While that’s open, try another query.
You only have the clustered index on ID,
the white pages of the table.
What’s the execution plan for this query:
SELECT Id
FROM dbo.Users
WHERE LastAccessDate >= '1800/01/01'
AND LastAccessDate <= '1800/01/02'
SQL Server’s execution plan
We don’t have an index on LastAccessDate,
so we’re going to have to scan the table for this.
If we run it, what happens?
Our SELECT just sits there blocked.
The update on the left has locks on some rows in the
Users table.
Until that lock is released, we don’t know whether the
locked rows match our WHERE clause filter.
So we’ll wait. Forever.
Let’s try another query.
You only have the clustered index on ID,
the white pages of the table.
What’s the execution plan for this query:
SELECT *
FROM dbo.Users
WHERE Id = 26837
Will it work?
Some of the rows of the clustered index (white pages)
are locked, but not Brent’s row.
Can Brent seek in and get unlocked data?
Add NOLOCK to our SELECT query
Commit our UPDATE transaction faster
Add an index on LastAccessDate
Change the database’s isolation level
Let’s index LastAccessDate.
Nonclustered indexes are like separate copies of the
table, with just the fields we want.
Cancel (roll back) the update, and create this index:
CREATE INDEX
IX_LastAccessDate_Id
ON dbo.Users(LastAccessDate, Id)
Try your update – what’s the plan?
BEGIN TRAN
UPDATE dbo.Users
SET Reputation = Reputation + 100
WHERE LastAccessDate >= '2013/11/10'
AND LastAccessDate <= '2013/11/11'
Your execution plan:
1. Use the new index on LastAccessDate – seek
directly to 2013/11/10, make a list of rows that
match.
2. Look up their IDs in the clustered index (white
pages), lock them, and update them.
3. After we finish, hold the locks until we commit.
Here’s the plan.
But it doesn’t show locks.
Do you need to lock the index (black pages)?
Let’s find out: run another query.
What’s the execution plan for this query:
SELECT Id
FROM dbo.Users
WHERE LastAccessDate >= '1800/01/01'
AND LastAccessDate <= '1800/01/02'
The SELECT finishes immediately.
Presto! The black pages weren’t locked.
The SELECT finishes immediately.
Presto! The black pages weren’t locked.
But what if we query the same dates that we’re updating?
Still no blocking.
Our SELECT finishes instantly.
Indexes are amazing and the cure to all ills.
Now try this SELECT query.
What’s the execution plan for this query:
SELECT Id, Reputation
FROM dbo.Users
WHERE LastAccessDate >= '1800/01/01'
AND LastAccessDate <= '1800/01/02'
Your execution plan:
1. Use the new index on LastAccessDate – seek
directly to 1800/01/01, make a list of rows that
match. (There won’t be any, right?)
2. Look up their IDs in the clustered index (white
pages), and get their Reputation field
Will it blend be blocked?
It finishes instantly!
Of course, no rows are returned.
But what happens if we look at 2013/11/10?
Awww, shucks.
The SELECT on the right is blocked because we need
Reputation, which is currently being edited.
But what if we skip Reputation, and get Location?
It’s still blocked.
The lock is on the clustered index row, not specific fields.
So if I wanted to query Id and Location, while I was
updating Reputation, and not get blocked, what could I do?
SQL Server locks individual indexes at the row level at first,
and only the relevant indexes – not all of ‘em.
Indexes are like readable replicas inside our DB.
Avoid indexing “hot” fields if you can.
Even just including “hot” fields comes at a price.
Let’s reward more people.
In your transaction window, let’s add another update
without committing it.
What’s the execution plan for this query:
BEGIN TRAN
UPDATE dbo.Users
SET Reputation = Reputation + 100
WHERE LastAccessDate >= '2014/11/10'
AND LastAccessDate <= '2014/11/11'
Still the same execution plan.
So far, so good.
While that’s open, run another query
You’ve run this one before – but let’s try it again:
SELECT *
FROM dbo.Users
WHERE Id = 26837
Still finishes instantly.
We do our clustered index seek and get out.
Brent is unaffected because of his LastAccessDate.
Let’s do one more round of gifts.
In your transaction window, let’s add another update
without committing it.
What’s the execution plan for this query:
BEGIN TRAN
UPDATE dbo.Users
SET Reputation = Reputation + 100
WHERE LastAccessDate >= '2015/11/10'
AND LastAccessDate <= '2015/11/11'
Brent’s still unaffected, right?
Yes, you’ve run this one before – but let’s try it again:
SELECT *
FROM dbo.Users
WHERE Id = 26837
Wait a minute - literally
Brent shouldn’t be blocked, but this SELECT hangs.
SQL Server has gone from locking individual rows of
the clustered index, up to locking the entire index.
Can we query by LastAccessDate?
Nope, that’s blocked too now, even for 1800.
Our row-level locks got escalated to table locks.
That escalated quickly
SQL Server needs memory to track locks.
When queries hold thousands of row-level locks,
SQL Server escalates those locks to table-level.
Depending on what day(s) of data you’re updating,
you might get row-level or table-level locks.
Additional considerations
Is your data growing over time?
Do your deletes/updates/inserts affect larger and
larger numbers of rows?
Are you using variable batch sizes?
Is your WHERE clause SARGable?
Are you doing a function row-by-row,
like DATEPART(month, LastAccessDate) = 7
Recap: right-sizing indexes
Indexes aren’t just great for selects:
they can make DUI operations faster, too.
You want the right number of indexes to support all of
your concurrent operations, but no more than that.
Understand how lock escalation can affect you.
Before tuning specific queries, use sp_BlitzIndex to:
• Remove indexes that aren’t getting used
• Put a clustered index on OLTP tables w/DUIs
• Add high-value indexes that are really needed
Next up: #2
1. Have enough indexes to make your queries fast,
but not so many that they slow down DUIs,
making them hold more locks for longer times.
2. Use the right isolation level for your app’s needs.
3. Identify queries involved in blocking & deadlocks
with sp_BlitzLock, then tune them.
SQL Server is pessimistic by default
I’ve got a
bad feeling
about this
An update starts
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
It takes out locks
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
Before it completes,
a read comes in…
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
SELECT COUNT(*)
FROM dbo.Pills
WHERE Color=‘Black’
It’s blocked
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
SELECT COUNT(*)
FROM dbo.Pills
WHERE Color=‘Black’
Until the update releases its locks
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
SELECT COUNT(*)
FROM dbo.Pills
WHERE Color=‘Black’
The count
is 1!
But you can choose optimism.
Life is good.
An update starts
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
It takes out locks (this is still true)
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
SQL Server saves the original
version in TempDB
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
Pointer
Before it completes,
a read comes in…
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
SELECT COUNT(*)
FROM dbo.Pills
WHERE Color=‘Black’
Pointer
It follows the version pointer!
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
SELECT COUNT(*)
FROM dbo.Pills
WHERE Color=‘Black’
Pointer
Pointer
They complete at the same time
UPDATE dbo.Pills
SET Color=‘White’
WHERE PillID=2;
SELECT COUNT(*)
FROM dbo.Pills
WHERE Color=‘Black’
The count
is 2!
The read completes independently
Under optimistic locking:
• Writers don’t block readers
• Readers don’t block writers
Which means the select sees different results:
• Pessimistic locking: 1 black pill, finishes fast
• Optimistic locking: 2 black pills, waits
But if both SELECTS had started juuuuust before the
update, or after it finished, they’d have seen the same
data.
Two ways to implement
Read Committed
Snapshot Isolation
Snapshot Isolation
Commonly called “RCSI” “Snapshot”
What does it do?
Changes the default isolation
level for an entire database to
optimistic locking
Allows individual
transactions to use
optimistic locking
Affects TempDB Yes Yes
Requires code
changes
No Yes
Requires testing Yes
Yes, but only the queries
who choose to use it
Learn more: BrentOzar.com/go/rcsi
Next up: #3
1. Have enough indexes to make your queries fast,
but not so many that they slow down DUIs,
making them hold more locks for longer times.
2. Use the right isolation level for your app’s needs.
3. Identify queries involved in blocking & deadlocks
with sp_BlitzLock, then tune them.
Traditional lock troubleshooting
Blocked process report:
http://michaeljswart.com/tag/blocked-process-report/
Profiler traces and Extended Events
Monitoring tools w/email alerts
But all these require setup ahead of time.
sp_BlitzLock
Free open source diagnostic tool:
• BrentOzar.com/first-aid/
• Github repo: FirstResponderKit.org
Uses the already-enabled system health XE session
Works on SQL Server 2012 & newer
Could work on 2008-2008R2, but will require code,
and if you wanna build it, hey, it’s open source!
Default output
Plus analysis, too
Tuning transactional queries
From easiest to hardest to implement:
1. Index tables involved to avoid slow table scans,
improve the CE’s estimation guesses
2. Keep transactions short and sweet
3. Work on tables in a predictable order
4. When working in batches, tune logic & size:
http://michaeljswart.com/2014/09/take-care-
when-scripting-batches/
5. Implement error handling and retry logic:
http://www.sommarskog.se/error_handling/Part
3.html
What you pretended to already know
1. Have enough indexes to make your queries fast,
but not so many that they slow down DUIs,
making them hold more locks for longer times.
2. Use the right isolation level for your app’s needs.
3. Identify queries involved in blocking & deadlocks
with sp_BlitzLock, then tune them.
Slides & demos: BrentOzar.com/go/locking
Two things I want from you
1. Tip your speakers:
fill out their feedback
forms, help us get better
2. Go to the community
corner:
ask how you can volunteer
to help make the next
SQLBits happen

More Related Content

Headaches of Blocking, Locking, and Deadlocking

  • 3. Glossary Locking: Larry takes out a lock. Blocking: Sarah needs a lock, but Larry has it. SQL Server will let Sarah wait for forever, and the symptom is LCK* waits. Deadlocks: Larry has locks, but needs some held by Sarah. Sarah has locks, but needs some held by Larry. SQL Server solves this one by killing somebody, and the symptom is dead bodies everywhere.
  • 4. 3 ways to fix blocking & deadlocks 1. Have enough indexes to make your queries fast, but not so many that they slow down DUIs, making them hold more locks for longer times. 2. Use the right isolation level for your app’s needs. 3. Identify queries involved in blocking & deadlocks with sp_BlitzLock, then tune them.
  • 5. The database we’ll use Open source, licensed with Creative Commons Full 100+GB version and mini 10GB version Learn more: BrentOzar.com/go/querystack
  • 6. Let’s query the Users table. You only have the clustered index on ID, the white pages of the table. How many accessed the system on my birthday?
  • 7. Let’s reward them. You only have the clustered index on ID, the white pages of the table. What’s the execution plan for this query: BEGIN TRAN UPDATE dbo.Users SET Reputation = Reputation + 100 WHERE LastAccessDate >= '2013/11/10' AND LastAccessDate <= '2013/11/11'
  • 8. Your execution plan: 1. Shuffle through all of the pages, examining the LastAccessDate. 2. If it matches our filter, lock this row and update it. 3. Keep moving through the table. 4. After we finish, hold the locks until we commit.
  • 9. SQL Server’s execution plan It does show the clustered index scan, but doesn’t show our locks.
  • 10. While that’s open, try another query. You only have the clustered index on ID, the white pages of the table. What’s the execution plan for this query: SELECT Id FROM dbo.Users WHERE LastAccessDate >= '1800/01/01' AND LastAccessDate <= '1800/01/02'
  • 11. SQL Server’s execution plan We don’t have an index on LastAccessDate, so we’re going to have to scan the table for this. If we run it, what happens?
  • 12. Our SELECT just sits there blocked. The update on the left has locks on some rows in the Users table. Until that lock is released, we don’t know whether the locked rows match our WHERE clause filter. So we’ll wait. Forever.
  • 13. Let’s try another query. You only have the clustered index on ID, the white pages of the table. What’s the execution plan for this query: SELECT * FROM dbo.Users WHERE Id = 26837
  • 14. Will it work? Some of the rows of the clustered index (white pages) are locked, but not Brent’s row. Can Brent seek in and get unlocked data?
  • 15. Add NOLOCK to our SELECT query Commit our UPDATE transaction faster Add an index on LastAccessDate Change the database’s isolation level
  • 16. Let’s index LastAccessDate. Nonclustered indexes are like separate copies of the table, with just the fields we want. Cancel (roll back) the update, and create this index: CREATE INDEX IX_LastAccessDate_Id ON dbo.Users(LastAccessDate, Id)
  • 17. Try your update – what’s the plan? BEGIN TRAN UPDATE dbo.Users SET Reputation = Reputation + 100 WHERE LastAccessDate >= '2013/11/10' AND LastAccessDate <= '2013/11/11'
  • 18. Your execution plan: 1. Use the new index on LastAccessDate – seek directly to 2013/11/10, make a list of rows that match. 2. Look up their IDs in the clustered index (white pages), lock them, and update them. 3. After we finish, hold the locks until we commit.
  • 19. Here’s the plan. But it doesn’t show locks. Do you need to lock the index (black pages)?
  • 20. Let’s find out: run another query. What’s the execution plan for this query: SELECT Id FROM dbo.Users WHERE LastAccessDate >= '1800/01/01' AND LastAccessDate <= '1800/01/02'
  • 21. The SELECT finishes immediately. Presto! The black pages weren’t locked.
  • 22. The SELECT finishes immediately. Presto! The black pages weren’t locked. But what if we query the same dates that we’re updating?
  • 23. Still no blocking. Our SELECT finishes instantly. Indexes are amazing and the cure to all ills.
  • 24. Now try this SELECT query. What’s the execution plan for this query: SELECT Id, Reputation FROM dbo.Users WHERE LastAccessDate >= '1800/01/01' AND LastAccessDate <= '1800/01/02'
  • 25. Your execution plan: 1. Use the new index on LastAccessDate – seek directly to 1800/01/01, make a list of rows that match. (There won’t be any, right?) 2. Look up their IDs in the clustered index (white pages), and get their Reputation field Will it blend be blocked?
  • 26. It finishes instantly! Of course, no rows are returned. But what happens if we look at 2013/11/10?
  • 27. Awww, shucks. The SELECT on the right is blocked because we need Reputation, which is currently being edited. But what if we skip Reputation, and get Location?
  • 28. It’s still blocked. The lock is on the clustered index row, not specific fields. So if I wanted to query Id and Location, while I was updating Reputation, and not get blocked, what could I do?
  • 29. SQL Server locks individual indexes at the row level at first, and only the relevant indexes – not all of ‘em. Indexes are like readable replicas inside our DB. Avoid indexing “hot” fields if you can. Even just including “hot” fields comes at a price.
  • 30. Let’s reward more people. In your transaction window, let’s add another update without committing it. What’s the execution plan for this query: BEGIN TRAN UPDATE dbo.Users SET Reputation = Reputation + 100 WHERE LastAccessDate >= '2014/11/10' AND LastAccessDate <= '2014/11/11'
  • 31. Still the same execution plan. So far, so good.
  • 32. While that’s open, run another query You’ve run this one before – but let’s try it again: SELECT * FROM dbo.Users WHERE Id = 26837
  • 33. Still finishes instantly. We do our clustered index seek and get out. Brent is unaffected because of his LastAccessDate.
  • 34. Let’s do one more round of gifts. In your transaction window, let’s add another update without committing it. What’s the execution plan for this query: BEGIN TRAN UPDATE dbo.Users SET Reputation = Reputation + 100 WHERE LastAccessDate >= '2015/11/10' AND LastAccessDate <= '2015/11/11'
  • 35. Brent’s still unaffected, right? Yes, you’ve run this one before – but let’s try it again: SELECT * FROM dbo.Users WHERE Id = 26837
  • 36. Wait a minute - literally Brent shouldn’t be blocked, but this SELECT hangs. SQL Server has gone from locking individual rows of the clustered index, up to locking the entire index.
  • 37. Can we query by LastAccessDate? Nope, that’s blocked too now, even for 1800. Our row-level locks got escalated to table locks.
  • 38. That escalated quickly SQL Server needs memory to track locks. When queries hold thousands of row-level locks, SQL Server escalates those locks to table-level. Depending on what day(s) of data you’re updating, you might get row-level or table-level locks.
  • 39. Additional considerations Is your data growing over time? Do your deletes/updates/inserts affect larger and larger numbers of rows? Are you using variable batch sizes? Is your WHERE clause SARGable? Are you doing a function row-by-row, like DATEPART(month, LastAccessDate) = 7
  • 40. Recap: right-sizing indexes Indexes aren’t just great for selects: they can make DUI operations faster, too. You want the right number of indexes to support all of your concurrent operations, but no more than that. Understand how lock escalation can affect you. Before tuning specific queries, use sp_BlitzIndex to: • Remove indexes that aren’t getting used • Put a clustered index on OLTP tables w/DUIs • Add high-value indexes that are really needed
  • 41. Next up: #2 1. Have enough indexes to make your queries fast, but not so many that they slow down DUIs, making them hold more locks for longer times. 2. Use the right isolation level for your app’s needs. 3. Identify queries involved in blocking & deadlocks with sp_BlitzLock, then tune them.
  • 42. SQL Server is pessimistic by default I’ve got a bad feeling about this
  • 43. An update starts UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2;
  • 44. It takes out locks UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2;
  • 45. Before it completes, a read comes in… UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2; SELECT COUNT(*) FROM dbo.Pills WHERE Color=‘Black’
  • 46. It’s blocked UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2; SELECT COUNT(*) FROM dbo.Pills WHERE Color=‘Black’
  • 47. Until the update releases its locks UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2; SELECT COUNT(*) FROM dbo.Pills WHERE Color=‘Black’ The count is 1!
  • 48. But you can choose optimism. Life is good.
  • 49. An update starts UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2;
  • 50. It takes out locks (this is still true) UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2;
  • 51. SQL Server saves the original version in TempDB UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2; Pointer
  • 52. Before it completes, a read comes in… UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2; SELECT COUNT(*) FROM dbo.Pills WHERE Color=‘Black’ Pointer
  • 53. It follows the version pointer! UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2; SELECT COUNT(*) FROM dbo.Pills WHERE Color=‘Black’ Pointer
  • 54. Pointer They complete at the same time UPDATE dbo.Pills SET Color=‘White’ WHERE PillID=2; SELECT COUNT(*) FROM dbo.Pills WHERE Color=‘Black’ The count is 2!
  • 55. The read completes independently Under optimistic locking: • Writers don’t block readers • Readers don’t block writers Which means the select sees different results: • Pessimistic locking: 1 black pill, finishes fast • Optimistic locking: 2 black pills, waits But if both SELECTS had started juuuuust before the update, or after it finished, they’d have seen the same data.
  • 56. Two ways to implement Read Committed Snapshot Isolation Snapshot Isolation Commonly called “RCSI” “Snapshot” What does it do? Changes the default isolation level for an entire database to optimistic locking Allows individual transactions to use optimistic locking Affects TempDB Yes Yes Requires code changes No Yes Requires testing Yes Yes, but only the queries who choose to use it Learn more: BrentOzar.com/go/rcsi
  • 57. Next up: #3 1. Have enough indexes to make your queries fast, but not so many that they slow down DUIs, making them hold more locks for longer times. 2. Use the right isolation level for your app’s needs. 3. Identify queries involved in blocking & deadlocks with sp_BlitzLock, then tune them.
  • 58. Traditional lock troubleshooting Blocked process report: http://michaeljswart.com/tag/blocked-process-report/ Profiler traces and Extended Events Monitoring tools w/email alerts But all these require setup ahead of time.
  • 59. sp_BlitzLock Free open source diagnostic tool: • BrentOzar.com/first-aid/ • Github repo: FirstResponderKit.org Uses the already-enabled system health XE session Works on SQL Server 2012 & newer Could work on 2008-2008R2, but will require code, and if you wanna build it, hey, it’s open source!
  • 62. Tuning transactional queries From easiest to hardest to implement: 1. Index tables involved to avoid slow table scans, improve the CE’s estimation guesses 2. Keep transactions short and sweet 3. Work on tables in a predictable order 4. When working in batches, tune logic & size: http://michaeljswart.com/2014/09/take-care- when-scripting-batches/ 5. Implement error handling and retry logic: http://www.sommarskog.se/error_handling/Part 3.html
  • 63. What you pretended to already know 1. Have enough indexes to make your queries fast, but not so many that they slow down DUIs, making them hold more locks for longer times. 2. Use the right isolation level for your app’s needs. 3. Identify queries involved in blocking & deadlocks with sp_BlitzLock, then tune them. Slides & demos: BrentOzar.com/go/locking
  • 64. Two things I want from you 1. Tip your speakers: fill out their feedback forms, help us get better 2. Go to the community corner: ask how you can volunteer to help make the next SQLBits happen