Continuing the series of posts looking at how the optimizer matches and applies internal rules to refine a query plan.
The last post ended with this query plan:
The optimizer has pushed the predicate ProductNumber LIKE 'T%'
down from a Filter to the Index Scan on the Product
table, but it remains as a residual predicate.
We need to enable a new transformation rule (SelResToFilter) to allow the optimizer to rewrite the LIKE
as a predicate suitable for use in an index seek:
Notice that the LIKE
is now expressed in a SARGable form, and the original LIKE
predicate is now only evaluated on rows returned from the seek.
The remaining inefficiency is in scanning the whole Inventory
table for every row returned by the new Index Seek. At the moment, the join predicate (matching ProductId
between the two tables) is performed inside the Nested Loops operator. It would be much more efficient to perform a seek on the Inventory
table’s clustered index.
To achieve that, we need to do two things:
- Convert the naïve nested loops join to an index nested loops join (see Understanding Nested Loops Joins)
- Drive each
Inventory
table seek using the current value ofProduct.ProductId
The first requirement is met by a rule called JNtoIdxLookup. The second requirement means using a correlated loops join - also known as an Apply. The rule needed to transform our query to that form is AppIdxToApp.
With those two rules available to the optimizer, here’s the plan we get:
We are now pretty close to the optimal plan for the specific value in this query. The last step is to collapse the Compute Scalar into the Stream Aggregate.
You might remember that the purpose of the Compute Scalar is to ensure that the SUM
aggregate returns NULL
instead of zero if no rows are processed.
As it stands, the Compute Scalar is evaluating a CASE
statement based on the result of a COUNT(*)
performed by the Stream Aggregate. We can remove this Compute Scalar, and the need to compute COUNT(*)
, by normalising the GROUP BY
using a rule called NormalizeGbAgg.
Once that is done, we have the finished plan:
In the remaining posts in this series, I will show you how to customise the rules available to the optimizer, and explore more of the internals of query optimization.
This post is part of a series: Part1 | Part 2 | Part3 | Part4
© Paul White
email: SQLkiwi@gmail.com
twitter: @SQL_Kiwi
No comments:
Post a Comment
All comments are reviewed before publication.