You Come And Go
I go back and forth when it comes to Lookups.
On the one hand, I don’t think the optimizer uses them enough. There are times when hinting a nonclustered index, or re-writing a query to get it to use a nonclustered index can really help performance.
On the other hand, they can really exacerbate parameter sniffing problems, and can even lead to read queries blocking write queries. And quite often, they lead to people creating very wide indexes to make sure particular queries are covered.
It’s quite a tedious dilemma, and in the case of blocking and, as we’ll see, deadlocks, one that can be avoided with an optimistic isolation level Read Committed Snapshot Isolation, or Snapshot Isolation.
There are ways to repro this sort of deadlock that rely mostly on luck, but the brute force approach is easiest.
First, create an index that will only partially help out some of our queries:
CREATE INDEX dethklok ON dbo.Votes(VoteTypeId);
Next, get a couple queries that should be able to co-exist ready to run in a loop.
/* Selecter */ SET NOCOUNT ON; DECLARE @i INT, @PostId INT; SET @i = 0; WHILE @i < 10000 BEGIN SELECT @PostId = v.PostId, @i += 1 FROM dbo.Votes AS v WHERE v.VoteTypeId = 8; END;
/* Updater */ SET NOCOUNT ON; DECLARE @i INT = 0; WHILE @i < 10000 BEGIN UPDATE v SET v.VoteTypeId = 8 - v.VoteTypeId, @i += 1 FROM dbo.Votes AS v WHERE v.Id = 55537618; END;
After several seconds, the select query will hit a deadlock.
The reason, of course, if that these two queries compete for the same indexes:
The update query needs to update both indexes on the table, the read query needs to read from both indexes on the table, and they end up blocking each other:
We could fix this by expanding the index to also have PostId in it:
CREATE INDEX dethklok ON dbo.Votes(VoteTypeId, PostId);
Using an optimistic isolation level:
ALTER DATABASE StackOverflow2013 SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
Or rewriting the select query to use a hash or merge join:
/* Selecter */ SET NOCOUNT ON; DECLARE @i INT, @PostId INT; SET @i = 0; WHILE @i < 10000 BEGIN SELECT @PostId = v2.PostId, @i += 1 FROM dbo.Votes AS v INNER /*MERGE OR HASH*/ JOIN dbo.Votes AS v2 ON v.Id = v2.Id WHERE v.VoteTypeId = 8 END;
Thanks for reading!
A Word From Our Sponsors
First, a huge thank you to everyone who has bought my training so far. You all are incredible, and I owe all of you a drink.
Your support means a lot to me, and allows me to do nice stuff for other people, like give training away for free.
So far, I’ve donated $45k (!!!) worth of training to folks in need, no questions asked.
Next year, I’d like to keep doing the same thing. I’d also like to produce a whole lot more training to add value to the money you spend. In order to do that, I need to take time off from consulting, which isn’t easy to do. I’m not crying poor, but saying no to work for chunks of time isn’t easy for a one-person party.
I’m hoping that I can make enough in training bucks to make that possible.
Because this sale is extra sale-y, I’ve decided to name it after the blackest black known to man.
From today until December 31st, you can get all 25 hours of my recorded training content for just $100.00. If you click the link below to add everything to your cart, and use the discount code AllFor100 to apply a discount to your cart.
Some fine print: It only works if you add EVERYTHING. It’s a fixed amount discount code that you need to spend a certain amount to have kick in.
Thank for reading, and for your support.