Quantcast
Channel: Erik Darling – Brent Ozar Unlimited®
Viewing all articles
Browse latest Browse all 370

Tall Tales From Table Variables

$
0
0

Secret Squirrel

When you modify a table with multiple indexes, SQL Server may choose either a narrow plan, if it doesn’t think all that many rows are going to change, or a wide plan if it thinks many will.

In narrow plans, the work SQL Server has to do to modify many indexes is hidden from you. However, these plan choices are prone to the same issues with estimates that any other plan choices are. During a conversation about when temp tables or table variables are appropriate, it came up that table variables are better for modification queries, because not all the indexes had to be updated at once.

When we looked at the plan together, we all had a good laugh and no one wept into their lumbar supports.

Pantsburner

I created some nonclustered indexes on the Posts table that all had the Score column in them, somewhere. Without them, there wouldn’t be much of a story.

When we use this query to update…

BEGIN TRAN
	
	DECLARE @bad_idea TABLE (id INT NOT NULL)
	INSERT @bad_idea ( id )
	SELECT TOP 1 u.Id
	FROM dbo.Users AS u
	ORDER BY u.Reputation DESC
	
	UPDATE p
	SET p.Score += 1000
	FROM dbo.Posts AS p
	JOIN @bad_idea AS bi
	ON bi.id = p.OwnerUserId
	
	--ROLLBACK

We get this plan…

itsy bitsy teenie weenie LIAR

If you’re playing along at home, the single row estimate that comes out of the Hash Match persists along the plan path right up to the Clustered Index Update.

Since a one row modification likely won’t qualify for a per-index update, all of the updated objects are stashed away behind the Clustered Index Update.

It’s a cover up, Scully. This one goes all the way up the plan tree.

Crackdown

Swapping our table variable out, and running this query…

BEGIN TRAN
	
	CREATE TABLE #better_idea (id INT NOT NULL)
	INSERT #better_idea ( id )
	SELECT TOP 1 u.Id
	FROM dbo.Users AS u
	ORDER BY u.Reputation DESC
	
	UPDATE p
	SET p.Score += 1000
	FROM dbo.Posts AS p
	JOIN #better_idea AS bi
	ON bi.id = p.OwnerUserId
	
	--ROLLBACK

We get a much more honest plan…

Best Policy

The estimates are accurate, so the optimizer chooses the wide plan.

I can see why this would scare some people, and they’d want to use the table variable.

The thing is, they both have to do the same amount of work.

Warnings

If you use our First Responder Kit, you may see warnings from sp_BlitzCache about plans that modify > 5 indexes. In sp_BlitzIndex, we warn about this in a bunch of different ways. Aggressive locking, unused indexes, indexes with a poor read to write ratio, tables with > 7 indexes, etc.

You can validate locking issues by sp_BlitzFirst and looking at your wait stats. If you see lots of LCK_ waits piling up, you’ve got some work to do, and I don’t mean adding NOLOCK to all your queries.


Viewing all articles
Browse latest Browse all 370

Trending Articles