When we are analyzing execution plans, we may come across different types of Spool operators - Table Spools, Row Count Spools, Window Spools or Index Spools – that the Query Optimizer chooses for specific purposes. In this post we are going to briefly look into the Index Spool, how it can sometimes lead to suboptimal query performance, and what can be done to easily fix it.
In loose terms, an Index Spool is a narrower indexed copy of a table, that is internally created on the tempdb to help handling repetitive operations. The interesting pattern regarding Index Spools, which we will further analyze in this post, is that they are often a sign that we lack an index to properly support our query. To overcome this, an Index Spool might be internally used, and, despite the possibility of this not representing a performance issue on certain workloads, it is important to be aware of some of its characteristics to have a better understanding of why it can affect performance:
This doesn’t necessarily mean that Index Spools are intrinsically bad or that we will always need to create a new index when we see one. But it’s important to understand why it can sometimes be a bottleneck in the execution plan and what can be done to improve performance when we identify it as such.
As an example of such scenario, let’s consider the following query and the resultant execution plan below:
SELECT
sod.ProductID,
x.LastTrack
FROM Sales.SalesOrderDetail sod
CROSS APPLY
(
SELECT MAX(OrderTrackingId) LastTrack
From Sales.OrderTracking t
WHERE sod.SalesOrderID = t.SalesOrderID
AND sod.ModifiedDate < t.EventDateTime
) x
WHERE ModifiedDate > '2013-01-01 00:00:00.0000000'
On the execution plan it’s possible to identify that the Index Scan plus the Index Spool represent almost all the query execution time. Below the execution plan itself, we can see three grids with the query execution stats and relevant information specific to the Index Spool:
Logical Reads |
CPU (ms) |
Elapsed time (ms) |
2 470 348 |
4124 |
3570 |
Fixing the Spool
As stated earlier, one interesting pattern regarding Index Spools is that they often cover a missing index to efficiently support the underlying query. If we look further at the Index Spool properties of the same execution plan shown above, we can easily identify the relevant columns for the index we need to create (note that some properties were omitted on the image below):
We can notice a Seek Predicate on the Keys SalesOrderID and EventDateTime, and also an Output List that includes OrderTrackingID. Mapping them to the index we need to create is very straightforward:
In this specific case, we don’t need to explicitly include the OrderTrackingID column since it is the Clustered Index of the Sales.OrderTracking table and it will be implicitly included.
With that we can create a new index, as it follows:
CREATE INDEX IX_SalesOrderID_EventDateTime on sales.OrderTracking (SalesOrderID,EventDateTime)
After executing the same query again, we will be able to confirm that the Index Spool is gone:
Logical Reads |
CPU (ms) |
Elapsed time (ms) |
269 009 |
821 |
261 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.