Pennies From Redmond
We’re spoiled over here in SQL Server land. When we run queries, the optimizer will suggest indexes that might help it run faster. I say might, because sometimes it’s wrong. Like I mentioned before, these suggestions are a bit hasty.
If you read the limitations, the first thing they say is “It is not intended to fine tune an indexing configuration”.
And they’re not. But you know what? If you don’t have anyone who is fine tuning an indexing configuration, they’re a heck of a lot better than a whole lot of nothing.
Are You There, Optimizer?
Starting with no nonclustered indexes, and a clustered index on an unrelated column, this query is a prime candidate for an index. And yet, the optimizer has forsaken us in our time of need.
SELECT COUNT_BIG(*) AS records FROM dbo.Comments AS c WHERE COALESCE(c.UserId, 0) = 0;
This isn’t specific to
COALESCE, any ol’ function — built-in or otherwise — would do it. But the optimizer doesn’t think an index would make this query any faster. That’s a real bummer.
You may hear people say things like “you can’t use indexes with functions”, but that sentence is incomplete. What’s meant is “you can’t use indexes to seek with functions”.
This goes back to the concept of SARGability that I talked about before. If we do a little bit more typing, the optimizer is our friend again. I know, typing more sucks, but you’ll thank me later.
SELECT COUNT_BIG(*) AS records FROM dbo.Comments AS c WHERE ( c.UserId = 0 OR c.UserId IS NULL );
The optimizer is back to having good ideas for us. But notice that both of these queries end up taking the same amount of time and doing the same amount of work.
If you were just getting started writing queries, performance tuning, or looking at query plans, you might see this and assume that query performance would remain identical.
It’s sort of a matter of opportunity, here. Like I said before, when you wrap predicates in functions, you lose the ability to seek into indexes.
Sure, you could use an index on UserId in either query, but it doesn’t usually fix a whole lot if you need to scan the entire thing.
If we add in the index on UserId and compare plans, the difference is obvious.
Our clearly-expressed query does a lot better here. It’s able to seek, and even running with a serial plan is much faster than the parallel plan for the poorly-expressed query. These things do matter to performance. Functions, even built-in ones, have no relational meaning to the optimizer, and can’t be reasoned with.
When it comes to writing queries, it usually pays to over-communicate. When you take shortcuts, you might not get the best possible outcome.
Tomorrow, we’ll look at hidden missing index requests.
Thanks for reading!
If this is the kind of SQL Server stuff you love learning about, you’ll love my training. I’m offering a 75% discount to my blog readers if you click from here. I’m also available for consulting if you just don’t have time for that and need to solve performance problems quickly.