Query Path 3 — Range Seek Query
The third query path selects a narrow range of consecutive values using a between operator in the where clause:
SELECT *
FROM Production.WorkOrder
WHERE WorkOrderID BETWEEN 10000 AND 10010;
The query optimizer must first determine if
there's a suitable index to select the range. In this case it's the
same key column in the clustered index as in the Query Path 2.
A range seek query has an interesting query
execution plan. The seek predicate (listed in the index seek
properties), which defines how the query is navigating the B-tree, has
both a start and an end to the seek predicate, as shown in Figure 3.
This means the operation is seeking the first row and then quickly
scanning and returning every row to the end of the range (refer to Figure 4).
To further investigate the range seek query path,
this next query pushes the range to the limit by selecting every row in
the table. And both queries are tested just to prove that between is logically the same as >= with <=:
SELECT *
FROM Production.WorkOrder
WHERE WorkOrderID >= 1 and WorkOrderID <= 72591;
SELECT *
FROM Production.WorkOrder
WHERE WorkOrderID between 1 and 72591;
At first glance it would seem that this query should generate the same query execution plan as the first query path (select * from table), but just like the narrow range query, the between operator needs a consecutive range of rows, and this causes the query optimizer to select index seek to return ordered rows.
There's no guarantee that another row might be
added after the query plan is generated and before it's executed.
Therefore, for range queries, an index seek is the fastest possible way
to ensure that only the correct rows are selected.
Index seeks and index scans both perform well
when returning large sets of data. The minor difference between the two
queries' durations listed in the performance chart (refer to Table 1)
is more likely variances in my computer's performance. There were some
iterations of the index seek that performed faster than some iterations
of the index scan.
Query Path 4 — Filter by Nonkey Column
In the previous query paths, the
clustered index key was used in the query predicate to find the rows.
Because the query predicate matched the clustered index key column, all
the data was available using a simple clustered key. But what if that
isn't the case?
Consider this query:
SELECT *
FROM Production.WorkOrder
WHERE StartDate = ‘07/15/2007'
There's no index with a key column of StartDate.
This means that the query optimizer can't choose an index to satisfy
the query and must resort to scanning the entire table and then
manually searching for rows that match the where clause. Without an index, this query path is 23 times slower than the clustered index seek query path.
The cost isn't the filter operation alone. (It's
only 7 percent of the total query cost.) The real cost is having to
scan in every row and pass 72,591 rows to the filter operation, as
shown in the query execution plan in Figure 5.
Management Studio suggests a missing index could
potentially help this query execute faster. Management Studio can even
generate the code to create the missing index using the context menu.
Warning
Use care when considering
implementing these suggested indexes. These index suggestions are
suited specifically for the query being investigated and often proves
to not be the best index for your overall indexing strategy. Too often
the missing index is not the best index, and it often wants to build a
nonclustered index that includes every column.