<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Craig Freedman's SQL Server Blog : Scans and Seeks</title><link>http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx</link><description>Tags: Scans and Seeks</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>What is the difference between sys.dm_db_index_usage_stats and sys.dm_db_index_operational_stats?</title><link>http://blogs.msdn.com/craigfr/archive/2008/10/30/what-is-the-difference-between-sys-dm-db-index-usage-stats-and-sys-dm-db-index-operational-stats.aspx</link><pubDate>Fri, 31 Oct 2008 00:40:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9025348</guid><dc:creator>craigfr</dc:creator><slash:comments>4</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/9025348.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=9025348</wfw:commentRss><description>&lt;P&gt;SQL Server includes two DMVs - sys.dm_db_index_usage_stats and sys.dm_db_index_operational_stats - that are extremely useful for monitoring which indexes are used as well as how and when they are used.&amp;nbsp; Both DMVs report similar statistics on information such as the number of scans, seeks, and updates to different indexes.&amp;nbsp; These DMVs are documented in Books Online (see &lt;A title=sys.dm_db_index_usage_stats href="http://msdn.microsoft.com/en-us/library/ms188755.aspx" mce_href="http://msdn.microsoft.com/en-us/library/ms188755.aspx"&gt;here&lt;/A&gt; and &lt;A title=sys.dm_db_index_operational_stats href="http://msdn.microsoft.com/en-us/library/ms174281.aspx" mce_href="http://msdn.microsoft.com/en-us/library/ms174281.aspx"&gt;here&lt;/A&gt;) and a simple Web search reveals numerous other postings about these DMVs.&amp;nbsp; However, in my own search, I did not find many direct explanations of the difference between these two valuable DMVs.&amp;nbsp; (You will find a short explanation halfway through &lt;A title="How can SQL Server 2005 help me evaluate and manage indexes?" href="http://blogs.msdn.com/sqlcat/archive/2006/02/13/531339.aspx" mce_href="http://blogs.msdn.com/sqlcat/archive/2006/02/13/531339.aspx"&gt;this post&lt;/A&gt; on the Microsoft SQL Server Customer Advisory Team blog.)&lt;/P&gt;
&lt;P&gt;The main difference between these DMVs is simple but important:&lt;/P&gt;
&lt;P&gt;sys.dm_db_index_usage_stats records how many times the query optimizer uses an index in a plan.&amp;nbsp; This usage information is recorded again each time the plan is executed.&amp;nbsp; (Compiling a plan alone is not sufficient to record an index's usage.)&amp;nbsp; However, and this is the important part, for the purposes of computing the statistics, it does matter how many times the query processor executes the specific operator that references the index.&amp;nbsp; For that matter, it does not matter whether the query processor executes the operator at all.&amp;nbsp; Mere execution of the plan counts as a single usage for each index used by the plan.&lt;/P&gt;
&lt;P&gt;sys.dm_db_index_operational_stats records how many times the storage engine executes a specific operation on the index.&amp;nbsp; These statistics do depend on how many times the query processor executes each operator.&amp;nbsp; If an operator is never executed, the storage engine does not perform any operations on the index and the DMV reports that the index was not used.&amp;nbsp; If an operator is executed multiple times, the storage engine performs multiple operations on the index and the DMV reports that the index was used multiple times.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Update (7/29/2009): The following paragraph is incorrect.&amp;nbsp; See &lt;/STRONG&gt;&lt;A title="Correction to my prior post on sys.dm_db_index_operational_sta" href="http://blogs.msdn.com/craigfr/archive/2009/07/29/correction-to-my-prior-post-on-sys-dm-db-index-operational-stats.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2009/07/29/correction-to-my-prior-post-on-sys-dm-db-index-operational-stats.aspx"&gt;&lt;STRONG&gt;this post&lt;/STRONG&gt;&lt;/A&gt;&lt;STRONG&gt; for more information.&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRIKE&gt;(Another less important difference between these DMVs is that sys.dm_db_index_usage_stats only reports on indexes that have been used at least once since the server was last restarted while sys.dm_db_index_operational_stats reports on all indexes regardless of whether they have been used.)&lt;/STRIKE&gt;&lt;/P&gt;
&lt;P&gt;Let's try an example to see this difference in action.&amp;nbsp; I'll use the following simple schema:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE TABLE T (A INT, B INT, C INT)&lt;BR&gt;CREATE UNIQUE CLUSTERED INDEX TA ON T(A)&lt;BR&gt;CREATE UNIQUE INDEX TB ON T(B)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;As expected, immediately after creating this table, the stats are zero (or just non-existent):&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT index_id, user_seeks, user_scans, user_lookups, user_updates&lt;BR&gt;FROM sys.dm_db_index_usage_stats&lt;BR&gt;WHERE database_id = DB_ID('tempdb') and object_id = OBJECT_ID('tempdb..t')&lt;BR&gt;ORDER BY index_id&lt;/P&gt;
&lt;P&gt;SELECT index_id, range_scan_count, singleton_lookup_count&lt;BR&gt;FROM sys.dm_db_index_operational_stats (DB_ID('tempdb'), OBJECT_ID('tempdb..t'), NULL, NULL)&lt;BR&gt;ORDER BY index_id&lt;/P&gt;&lt;/BLOCKQUOTE&gt;&lt;PRE&gt;index_id    user_seeks           user_scans           user_lookups         user_updates
----------- -------------------- -------------------- -------------------- --------------------&lt;/PRE&gt;&lt;PRE&gt;index_id    range_scan_count     singleton_lookup_count
----------- -------------------- ----------------------
1           0                    0
2           0                    0&lt;/PRE&gt;
&lt;P&gt;Now suppose that we do a scan of the clustered index:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT * FROM T&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[T].[TA]))&lt;/P&gt;
&lt;P&gt;Repeating the DMV queries, we see that the clustered index shows one scan in both DMVs.&amp;nbsp; SQL Server records the scan even though the table contains no rows and the query returns an empty result:&lt;/P&gt;&lt;PRE&gt;index_id    user_seeks           user_scans           user_lookups         user_updates
----------- -------------------- -------------------- -------------------- --------------------
1           0                    1                    0                    0&lt;/PRE&gt;&lt;PRE&gt;index_id    range_scan_count     singleton_lookup_count
----------- -------------------- ----------------------
1           1                    0
2           0                    0&lt;/PRE&gt;
&lt;P&gt;Next let's try a singleton lookup on the clustered index:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT * FROM T WHERE A = 1&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[T].[TA]), SEEK:([tempdb].[dbo].[T].[A]=CONVERT_IMPLICIT(int,[@1],0)) ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;Again the table contains no rows and the query returns an empty result.&amp;nbsp; Nevertheless, the DMVs now report one seek and one singleton lookup:&lt;/P&gt;&lt;PRE&gt;index_id    user_seeks           user_scans           user_lookups         user_updates
----------- -------------------- -------------------- -------------------- --------------------
1           1                    1                    0                    0&lt;/PRE&gt;&lt;PRE&gt;index_id    range_scan_count     singleton_lookup_count
----------- -------------------- ----------------------
1           1                    1
2           0                    0&lt;/PRE&gt;
&lt;P&gt;(Keep in mind that the DMV results are cumulative so you need to subtract the previous values from the current values as you run each of these experiments.&amp;nbsp; Thus, we can disregard the scan that was already reported by the previous example.)&lt;/P&gt;
&lt;P&gt;Now let's try something a little more interesting.&amp;nbsp; Let's run a bookmark lookup:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT * FROM T WHERE B = 1&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([tempdb].[dbo].[T].[A]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Index Seek(OBJECT:([tempdb].[dbo].[T].[TB]), SEEK:([tempdb].[dbo].[T].[B]=CONVERT_IMPLICIT(int,[@1],0)) ORDERED FORWARD)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[T].[TA]), SEEK:([tempdb].[dbo].[T].[A]=[tempdb].[dbo].[T].[A]) LOOKUP ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;As expected sys.dm_db_index_usage_stats reports a seek on index TB (index id 2) and a bookmark lookup on the clustered index (index id 1).&amp;nbsp; However, sys.dm_db_index_operational_stats reports only the singleton lookup on index TB but does not report any new activity on the clustered index:&lt;/P&gt;&lt;PRE&gt;index_id    user_seeks           user_scans           user_lookups         user_updates
----------- -------------------- -------------------- -------------------- --------------------
1           1                    1                    1                    0
2           1                    0                    0                    0&lt;/PRE&gt;&lt;PRE&gt;index_id    range_scan_count     singleton_lookup_count
----------- -------------------- ----------------------
1           1                    1
2           0                    1&lt;/PRE&gt;
&lt;P&gt;To understand what has happened, recall how a &lt;A title="Nested Loops Join" href="http://blogs.msdn.com/craigfr/archive/2006/07/26/679319.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2006/07/26/679319.aspx"&gt;nested loops join&lt;/A&gt; works.&amp;nbsp; The server executes the seek (the singleton lookup) on index TB and, as in the previous example, both DMVs are updated even though the seek returns no rows.&amp;nbsp; However, since the seek on index TB returns no rows, the nested loops join does not execute the clustered index seek (i.e., the bookmark lookup).&amp;nbsp; The server updates sys.dm_db_index_usage_stats to indicate that it executed a query plan that includes a bookmark lookup on table T, but does not update sys.dm_db_index_operational_stats since the query did not actually perform any bookmark lookups.&lt;/P&gt;
&lt;P&gt;Next, let's insert three rows into the table and run another bookmark lookup experiment.&amp;nbsp; I'm using a hint to force a bookmark lookup plan.&amp;nbsp; Without the hint, the optimizer would simply use a clustered index scan since the query returns all three rows in the table:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;INSERT T VALUES (0, 0, 0), (1, 1, 1), (2, 2, 2)&lt;BR&gt;SELECT * FROM T WITH (INDEX (TB))&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([tempdb].[dbo].[T].[A]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Index Scan(OBJECT:([tempdb].[dbo].[T].[TB]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[T].[TA]), SEEK:([tempdb].[dbo].[T].[A]=[tempdb].[dbo].[T].[A]) LOOKUP ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;This time sys.dm_db_index_usage_stats reports a scan on index TB and a bookmark lookup on the clustered index (plus the updates from the insert statement).&amp;nbsp; But, sys.dm_db_index_operational_stats reports a scan on index TB and &lt;I&gt;three&lt;/I&gt; bookmark lookups on the clustered index:&lt;/P&gt;&lt;PRE&gt;index_id    user_seeks           user_scans           user_lookups         user_updates
----------- -------------------- -------------------- -------------------- --------------------
1           1                    1                    2                    1
2           1                    1                    0                    1&lt;/PRE&gt;&lt;PRE&gt;index_id    range_scan_count     singleton_lookup_count
----------- -------------------- ----------------------
1           1                    4
2           1                    1&lt;/PRE&gt;
&lt;P&gt;When the server executes the above query, it runs the clustered index seek three times - once for each row returned by the index scan.&amp;nbsp; We ran the query only once but it performed three bookmark lookups.&amp;nbsp; Thus, as in the prior example, the server updates sys.dm_db_index_usage_stats to indicate that it executed a query plan that includes a bookmark lookup on table T, but unlike the prior example, it updates sys.dm_db_index_operational_stats to indicate that the query performed three actual bookmark lookups.&lt;/P&gt;
&lt;P&gt;I've used bookmark lookups in the above examples, but any nested loops join will produce similar results.&amp;nbsp; At this point, it should be clear that the statistics returned by these two DMVs can differ dramatically.&lt;/P&gt;So, what is the important takeaway from all of these examples?&amp;nbsp;&amp;nbsp; Don't expect the data reported by these two DMVs to match.&amp;nbsp; sys.dm_db_index_usage_stats tells us the proportion of query plans that were executed that use various indexes.&amp;nbsp; This information is useful for concluding how many of the executed query plans might be affected if we drop an index but it does not tell us how many actual operations are performed using each index.&amp;nbsp; sys.dm_db_index_operational_stats, on the other hand, tells us how often the indexes are actually used during the execution of plans and, thus, which indexes are directly contributing to server performance.&amp;nbsp; But, even if sys.dm_db_index_operational_stats indicates that an index is not used very often (or perhaps even that an index is never used), do not automatically conclude that you can drop the index.&amp;nbsp; First, be sure that sys.dm_db_index_usage_stats indicates that no queries depend on the index.&amp;nbsp; In some cases, the presence of an index could change a query plan for the better even though the index itself is not used when the plan is executed.&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9025348" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category></item><item><title>Sequential Read Ahead</title><link>http://blogs.msdn.com/craigfr/archive/2008/09/23/sequential-read-ahead.aspx</link><pubDate>Tue, 23 Sep 2008 23:45:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8962716</guid><dc:creator>craigfr</dc:creator><slash:comments>9</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/8962716.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=8962716</wfw:commentRss><description>&lt;P&gt;Balancing CPU and I/O throughput is essential to achieve good overall performance and to maximize hardware utilization.&amp;nbsp; SQL Server includes two asynchronous I/O mechanisms - sequential read ahead and random prefetching - that are designed to address this challenge.&lt;/P&gt;
&lt;P&gt;To understand why asynchronous I/O is so important, consider the CPU to I/O performance gap.&amp;nbsp; The memory subsystem on a modern CPU can deliver data sequentially at roughly 5 Gbytes per second per socket (or for non-NUMA machines for all sockets sharing the same bus) and (depending on how you measure it) can fetch random memory locations at roughly 10 to 50 million accesses per second.&amp;nbsp; By comparison, a high end 15K SAS hard drive can read only 125 Mbytes per second sequentially and can perform only 200 random I/Os per second (IOPS).&amp;nbsp; Solid State Disks (SSDS) can reduce the gap between sequential and random I/O performance by eliminating the moving parts from the equation, but a performance gap remains.&amp;nbsp; In an effort to close this performance gap, it is not uncommon for servers to have a ratio of 10 or more drives for every CPU.&amp;nbsp; (It is also important to consider and balance the entire I/O subsystem including the number and type of disk controllers not just the drives themselves but that is not the focus of this post.)&lt;/P&gt;
&lt;P&gt;Unfortunately, a single CPU issuing only synchronous I/Os can keep only one spindle active at a time.&amp;nbsp; For a single CPU to exploit the available bandwidth and IOPs of multiple spindles effectively the server must issue multiple I/Os asynchronously.&amp;nbsp; Thus, SQL Server includes the aforementioned read ahead and prefetching mechanisms.&amp;nbsp; In this post, I'll take a look at sequential read ahead.&lt;/P&gt;
&lt;P&gt;When SQL Server performs a sequential &lt;A title="Scans vs. Seeks" href="http://blogs.msdn.com/craigfr/archive/2006/06/26/647852.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2006/06/26/647852.aspx"&gt;scan&lt;/A&gt; of a large table, the storage engine initiates the read ahead mechanism to ensure that pages are in memory and ready to scan before they are needed by the query processor.&amp;nbsp; The read ahead mechanism tries to stay 500 pages ahead of the scan.&amp;nbsp; We can see the read ahead mechanism in action by checking the output of SET STATISTICS IO ON.&amp;nbsp; For example, I ran the following query on a 1GB scale factor &lt;A href="http://www.tpc.org/tpch/default.asp" mce_href="http://www.tpc.org/tpch/default.asp"&gt;TPC-H&lt;/A&gt; database.&amp;nbsp; The LINEITEM table has roughly 6 million rows.&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SET STATISTICS IO ON&lt;/P&gt;
&lt;P&gt;SELECT COUNT(*) FROM LINEITEM&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;Table 'LINEITEM'. Scan count 3, logical reads 22328, physical reads 3, &lt;B&gt;read-ahead reads 20331&lt;/B&gt;, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Repeating the query a second time shows that the table is now cached in the buffer pool:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT COUNT(*) FROM LINEITEM&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;Table 'LINEITEM'. Scan count 3, logical reads 22328, physical reads 0, &lt;B&gt;read-ahead reads 0&lt;/B&gt;, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;For sequential I/O performance, it is important to distinguish between allocation ordered and index ordered scans.&amp;nbsp; An allocation ordered scan tries to read pages in the order in which they are physically stored on disk while an index ordered scan reads pages according to the order in which the data on those index pages is sorted.&amp;nbsp; (Note that in many cases there are multiple levels of indirection such as RAID devices or SANS between the logical volumes that SQL Server sees and the physical disks.&amp;nbsp; Thus, even an allocation ordered scan may in fact not be truly optimally ordered.)&amp;nbsp; Although SQL Server tries to sort and read pages in allocation order even for an index ordered scan, an allocation ordered scan is generally going to be faster since pages are read in the order that they are written on disk with the minimal number of seeks.&amp;nbsp; Heaps have no inherent order and, thus, are always scanned in allocation order.&amp;nbsp; Indexes are scanned in allocation order only if the isolation level is read uncommitted (or the NOLOCK hint is used) and only if the query process does not request an ordered scan.&amp;nbsp; Defragmenting indexes can help to ensure that index ordered scans perform on par with allocation ordered scans.&lt;/P&gt;In my next post, I'll take a look at random prefetching.&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8962716" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/I_2F00_O/default.aspx">I/O</category></item><item><title>Partitioned Indexes in SQL Server 2008</title><link>http://blogs.msdn.com/craigfr/archive/2008/08/05/partitioned-indexes-in-sql-server-2008.aspx</link><pubDate>Tue, 05 Aug 2008 21:33:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8834643</guid><dc:creator>craigfr</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/8834643.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=8834643</wfw:commentRss><description>&lt;P&gt;In &lt;A title="Partitioned Tables in SQL Server 2008" href="http://blogs.msdn.com/craigfr/archive/2008/07/15/partitioned-tables-in-sql-server-2008.aspx"&gt;my last post&lt;/A&gt;, I looked at how SQL Server 2008 handles scans on partitioned tables.&amp;nbsp; I explained that SQL Server 2008 treats partitioned tables as tables with a logical index on the partition id column and that SQL Server 2008 implements partition elimination by performing a logical index seek on the partition id column.&amp;nbsp; Specifically, I showed some examples using a heap.&amp;nbsp; In this post, I'll continue this discussion and explore how SQL Server 2008 handles scans and seek on partitioned indexes.&lt;/P&gt;
&lt;P&gt;Let's begin with a simple example:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE PARTITION FUNCTION PF(INT) AS RANGE FOR VALUES (0, 10, 100)&lt;BR&gt;CREATE PARTITION SCHEME PS AS PARTITION PF ALL TO ([PRIMARY])&lt;/P&gt;
&lt;P mce_keep="true"&gt;CREATE TABLE T1 (A INT, B INT)&lt;BR&gt;CREATE CLUSTERED INDEX T1A ON T1(A) ON PS(A)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;As I noted in my last post, the SQL Server query processor logically treats this partitioned index as a multi-column index on ([PtnId], A).&amp;nbsp; We can scan this logical index just like any real index:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT * FROM T1&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Clustered Index Scan(OBJECT:([T1].[T1A]))&lt;/P&gt;
&lt;P&gt;This query uses the same plan as any other clustered index scan.&amp;nbsp; It implicitly scans all of the partitions.&amp;nbsp; Aside from the "Partitioned" attribute in the graphical and XML plans, there is no difference between this plan and a similar clustered index scan on a non-partitioned table.&amp;nbsp; We can also perform a seek on the logical index:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;DECLARE @I INT&lt;BR&gt;SELECT @I = 0&lt;BR&gt;SELECT * FROM T1 WHERE A = @I&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Clustered Index Seek(OBJECT:([T1].[T1A]), SEEK:(&lt;B&gt;[PtnId1000]=RangePartitionNew([@I],(0),(0),(10),(100))&lt;/B&gt; AND [T1].[A]=[@I]) ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;Notice that SQL Server derives the predicate on the [PtnId] column from the explicit predicate on column A.&amp;nbsp; SQL Server then seeks on both columns of the logical index - [PtnId] and column A - just as if the logical index were a real index.&amp;nbsp; So far, everything is pretty straightforward.&amp;nbsp; Now, let's consider a slightly more complex scenario:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;DECLARE @I INT&lt;BR&gt;SELECT @I = 0&lt;BR&gt;SELECT * FROM T1 &lt;B&gt;WHERE A &amp;lt; @I&lt;/B&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Both this and the prior example have predicates on column A.&amp;nbsp; In both cases, SQL Server derives a predicate on the [PtnId] column.&amp;nbsp; However, in the prior example, the derived predicate was an equality predicate that limited the seek to a single partition.&amp;nbsp; In this example, due to the inequality, the derived predicate is actually a range of partitions:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Clustered Index Seek(OBJECT:([T1].[T1A]), SEEK:(&lt;B&gt;[PtnId1000] &amp;gt;= (1) AND [PtnId1000] &amp;lt;= RangePartitionNew([@I],(0),(0),(10),(100))&lt;/B&gt; AND [T1].[A] &amp;lt; [@I]) ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;If you are familiar with &lt;A title="Seek Predicates" href="http://blogs.msdn.com/craigfr/archive/2006/07/07/652668.aspx"&gt;the rules on index seeks&lt;/A&gt;, you will notice something odd about this plan.&amp;nbsp; Ordinarily, SQL Server can only perform an index seek on the second column of a multi-column index if there exists an equality predicate on the first column.&amp;nbsp; Clearly, there is not an equality predicate on the [PtnId] column in this query and yet there is a predicate on column A.&amp;nbsp; What's going on?&amp;nbsp; The query processor supports a special type of index seek on partitioned tables.&amp;nbsp; Basically, the query processor first uses the predicate on the [PtnId] column to identify those partitions into which to seek and then seeks again using the predicate on column A.&amp;nbsp; Because the seek operator &lt;I&gt;skips&lt;/I&gt; over some rows in each partition that do not match the predicate on column A, we refer to this type of seek as a &lt;I&gt;skip scan&lt;/I&gt;.&amp;nbsp; Note that this operation really is a seek - it is not a residual predicate.&amp;nbsp; The storage engine only returns those rows that pass both predicates.&amp;nbsp; Also note that SQL Server 2008 only supports this skip scan functionality on the [PtnId] column of a partitioned table.&lt;B&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;Now let's change the schema slightly:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE TABLE T2 (A INT, B INT)&lt;BR&gt;CREATE CLUSTERED INDEX T2A ON T2(A) &lt;B&gt;ON PS(B)&lt;/B&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;This new table is indexed on column A but partitioned on column B.&amp;nbsp; Once again, SQL Server logically treats the index as a multi-column index on ([PtnId], B).&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;DECLARE @I INT&lt;BR&gt;SELECT @I = 0&lt;BR&gt;SELECT * FROM T2 WHERE A = @I&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Since the table is partitioned on column B, we cannot use the predicate on column A to derive a predicate on the [PtnId] column.&amp;nbsp; Fortunately, the query processor can still perform a skip scan by adding a predicate on the [PtnId] column that explicitly scans all partitions:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Clustered Index Seek(OBJECT:([T2].[T2A]), SEEK:([PtnId1000] &amp;gt;= (1) AND [PtnId1000] &amp;lt;= (4) AND [T2].[A]=[@I]) ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;Finally, before I wrap up this post, I'd like to point out that, while it's not often useful, we can add an explicit predicate on the [PtnId] column of a table.&amp;nbsp; To do so, we use the $PARTITION function:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;DECLARE @PtnId INT&lt;BR&gt;SELECT @PtnId = 1&lt;BR&gt;SELECT * FROM T1 &lt;B&gt;WHERE $PARTITION.PF(A) = @PtnId&lt;/B&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Clustered Index Scan(OBJECT:([T1].[T1A]), SEEK:(&lt;B&gt;[PtnId1000]=[@PtnId]&lt;/B&gt;) ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;Just as with the derived [PtnId] predicates, SQL Server can perform a seek on the [PtnId] column.&amp;nbsp; When using this syntax, it is important to use the correct $PARTITION function and the correct column or SQL Server will not recognize it as the [PtnId] column for this table.&lt;/P&gt;&lt;A title="Query Processing Enhancements on Partitioned Tables and Indexes" href="http://technet.microsoft.com/en-us/library/ms345599(SQL.100).aspx"&gt;Books Online&lt;/A&gt; has more information on these and other partitioned table changes in SQL Server 2008.&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8834643" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Partitioned+Tables/default.aspx">Partitioned Tables</category></item><item><title>Partitioned Tables in SQL Server 2008</title><link>http://blogs.msdn.com/craigfr/archive/2008/07/15/partitioned-tables-in-sql-server-2008.aspx</link><pubDate>Wed, 16 Jul 2008 00:03:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8735112</guid><dc:creator>craigfr</dc:creator><slash:comments>8</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/8735112.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=8735112</wfw:commentRss><description>&lt;P&gt;In &lt;A title="Introduction to Partitioned Tables" href="http://blogs.msdn.com/craigfr/archive/2006/11/27/introduction-to-partitioned-tables.aspx"&gt;this post&lt;/A&gt;, I introduced how SQL Server 2005 implements query plans on partitioned tables.&amp;nbsp; If you've read that post or used partitioned tables, you may recall that SQL Server 2005 uses a constant scan operator to enumerate the list of partition ids that need to be scanned.&amp;nbsp; As a refresher, here is the example from that post showing the plan for scanning a table with four partitions:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE PARTITION FUNCTION PF(INT) AS RANGE FOR VALUES (0, 10, 100)&lt;BR&gt;CREATE PARTITION SCHEME PS AS PARTITION PF ALL TO ([PRIMARY])&lt;BR&gt;CREATE TABLE T (A INT, B INT) ON PS(A)&lt;/P&gt;
&lt;P&gt;SELECT * FROM T&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PARTITION ID:([PtnIds1004]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([t]))&lt;/P&gt;
&lt;P mce_keep="true"&gt;SQL Server 2005 treats partitioned tables specially and creates special plans, such as the above one, for partitioned tables.&amp;nbsp; SQL Server 2008, on the other hand, for the most part treats partitioned tables as regular tables that just happen to be logically indexed on the partition id column.&amp;nbsp; For example, for the purposes or query optimization and query execution, SQL Server 2008 treats the above table not as a heap but as an index on [PtnId].&amp;nbsp; If we create a partitioned index (clustered or non-clustered), SQL Server 2008 logically adds the partition id column as the first column of the index.&amp;nbsp; For instance, if we create the following index:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;CREATE INDEX TA ON T(A) ON PS(A)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;SQL Server 2008 treats it not as a single column index on T(A), but rather as a multi-column or composite index on T([PtnId],A). &amp;nbsp;Note that the table and index are still stored physically as partitioned tables.&amp;nbsp; In this example, the table and non-clustered index are decomposed into four heaps and four non-clustered indexes.&lt;/P&gt;
&lt;P&gt;By treating partitioned tables as indexes, SQL Server 2008 can frequently use much simpler query plans.&amp;nbsp; Let's look at the SQL Server 2008 plan for the above example:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;|--Table Scan(OBJECT:([T]))&lt;/P&gt;
&lt;P&gt;This query plan is no different than what you might see if you scanned any other ordinary table!&amp;nbsp; So, how can we tell that the table is really partitioned and how can we tell what partitions the plan is going to scan?&amp;nbsp; Luckily, the graphical and XML plans identify partitioned tables by adding a "Partitioned" attribute to any scan, seek, or update operator that processes a partitioned table.&amp;nbsp; Moreover, the actual graphical plan and the STATISTICS XML output identify the exact partitions that the plan touches during execution.&amp;nbsp; For example, here is an excerpt from the STATISTICS XML output generated by running the above query:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&amp;lt;RunTimePartitionSummary&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;PartitionsAccessed PartitionCount="4"&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;PartitionRange Start="1" End="4"/&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;/PartitionsAccessed&amp;gt;&lt;BR&gt;&amp;lt;/RunTimePartitionSummary&amp;gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;This output shows that, as expected, the query plan scanned all four partitions of the table.&amp;nbsp; Now, suppose we insert a row into the second partition (up until now, the table has been empty) and run the following slightly modified query:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;INSERT T VALUES (1, 1)&lt;/P&gt;
&lt;P&gt;SELECT TOP 1 * FROM T&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;This query can stop executing as soon as it finds a single row.&amp;nbsp; It scans the first partition and finding no rows continues on to scan the second partition.&amp;nbsp; At that point, it finds a row and stops.&amp;nbsp; Thus, the query terminates after scanning only two partitions.&amp;nbsp; This outcome is clearly reflected in the STATISTICS XML output:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&amp;lt;RunTimePartitionSummary&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;PartitionsAccessed &lt;B&gt;PartitionCount="2"&lt;/B&gt;&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;PartitionRange &lt;B&gt;Start="1" End="2"&lt;/B&gt;/&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;/PartitionsAccessed&amp;gt;&lt;BR&gt;&amp;lt;/RunTimePartitionSummary&amp;gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Now let's see how partition elimination works.&amp;nbsp; Let's start with static partition elimination:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;SELECT * FROM T WHERE A &amp;lt; 100&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp; |--Table Scan(OBJECT:([T]), &lt;B&gt;SEEK:([PtnId1001] &amp;gt;= (1) AND [PtnId1001] &amp;lt;= (3))&lt;/B&gt;,&amp;nbsp; WHERE:([T].[A]&amp;lt;(100)) ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;Because SQL Server 2008 treats the partitioned table like an index on [PtnId], it can simply calculate the range of partitions that need to be scanned and perform a "seek" on those partitions.&amp;nbsp; It may seem a little strange to see a SEEK predicate on a table scan, but it just means that the plan is going to scan partitions 1 through 3.&lt;/P&gt;
&lt;P&gt;And now, let's look at dynamic partition elimination:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;DECLARE @I INT&lt;BR&gt;SELECT @I = 0&lt;BR&gt;SELECT * FROM T WHERE A &amp;lt; @I&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp; |--Table Scan(OBJECT:([T]), SEEK:([PtnId1001] &amp;gt;= (1) AND &lt;B&gt;[PtnId1001] &amp;lt;= RangePartitionNew([@I],(0),(0),(10),(100))&lt;/B&gt;),&amp;nbsp; WHERE:([T].[A]&amp;lt;[@I]) ORDERED FORWARD)&lt;/P&gt;
&lt;P&gt;This plan is identical to the prior plan except that the constant partition id 3 has been replaced by the RangePartitionNew function which computes the correct partition id from the variable @I.&amp;nbsp; To find out exactly which partitions were scanned, we can once again check the STATISTICS XML output:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&amp;lt;RunTimePartitionSummary&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;PartitionsAccessed PartitionCount="1"&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;PartitionRange Start="1" End="1"/&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;/PartitionsAccessed&amp;gt;&lt;BR&gt;&amp;lt;/RunTimePartitionSummary&amp;gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Note that this plan computes exactly which partitions to scan.&amp;nbsp; Compare that to the SQL Server 2005 plan which evaluates a filter for each and every partition id:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PARTITION ID:([PtnIds1004]))&lt;BR&gt;&lt;B&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Filter(WHERE:([PtnIds1004]&amp;lt;=RangePartitionNew([@i],(0),(0),(10),(100))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))))&lt;BR&gt;&lt;/B&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([t]), WHERE:([t].[a]&amp;lt;[@i]) PARTITION ID:([PtnIds1004]))&lt;/P&gt;
&lt;P&gt;While evaluating the filter repeatedly may not cost too much if we have only four partitions, it is certainly going to waste some cycles if we have many partitions!&lt;/P&gt;In my next post, I'll take a look at how SQL Server 2008 handles scans and seeks on partitioned indexes.&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8735112" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Partitioned+Tables/default.aspx">Partitioned Tables</category></item><item><title>Query Processing Presentation</title><link>http://blogs.msdn.com/craigfr/archive/2008/05/15/query-processing-presentation.aspx</link><pubDate>Thu, 15 May 2008 19:39:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8508493</guid><dc:creator>craigfr</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/8508493.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=8508493</wfw:commentRss><description>&lt;P mce_keep="true"&gt;Last week, I had the opportunity to &lt;A class="" title="[New England] NESQL Special Meeting, Featuring Craig Freedman" href="http://sqlblog.com/blogs/adam_machanic/archive/2008/05/02/new-england-nesql-special-meeting-featuring-craig-freedman.aspx" mce_href="http://sqlblog.com/blogs/adam_machanic/archive/2008/05/02/new-england-nesql-special-meeting-featuring-craig-freedman.aspx"&gt;talk&lt;/A&gt; to the &lt;A title="New England SQL Server Users Group" href="http://www.nesql.org/"&gt;New England SQL Server Users Group&lt;/A&gt;.&amp;nbsp; I would like to thank the group for inviting me, &lt;A title="Adam Machanic" href="http://sqlblog.com/blogs/adam_machanic/"&gt;Adam Machanic&lt;/A&gt; for organizing the event, and &lt;A title="Red Gate" href="http://www.red-gate.com/"&gt;Red Gate&lt;/A&gt; for sponsoring it.&amp;nbsp; My talk was an introduction to query processing, query execution, and query plans in SQL Server.&amp;nbsp; I've had a request for the slides, so here they are.&amp;nbsp; Enjoy!&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8508493" width="1" height="1"&gt;</description><enclosure url="http://blogs.msdn.com/craigfr/attachment/8508493.ashx" length="759608" type="application/pdf" /><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Joins/default.aspx">Joins</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Aggregation/default.aspx">Aggregation</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Updates/default.aspx">Updates</category></item><item><title>Repeatable Read Isolation Level</title><link>http://blogs.msdn.com/craigfr/archive/2007/05/09/repeatable-read-isolation-level.aspx</link><pubDate>Wed, 09 May 2007 17:57:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:2504802</guid><dc:creator>craigfr</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/2504802.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=2504802</wfw:commentRss><description>&lt;P&gt;In my last two posts, I showed how queries running at read committed isolation level may generate unexpected results in the presence of concurrent updates.&amp;nbsp; Many but not all of these results can be avoided by running at repeatable read isolation level.&amp;nbsp; In this post, I'll explore how concurrent updates may affect queries running at repeatable read.&lt;/P&gt;
&lt;P&gt;Unlike a read committed scan, a repeatable read scan retains locks on every row it touches until the end of the transaction.&amp;nbsp; Even rows that do not qualify for the query result remain locked.&amp;nbsp; These locks ensure that the rows touched by the query cannot be updated or deleted by a concurrent session until the current transaction completes (whether it is committed or rolled back).&amp;nbsp; These locks do not protect rows that have not yet been scanned from updates or deletes and do not prevent the insertion of new rows amid the rows that are already locked.&amp;nbsp; The following graphic illustrates this point:&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/2504516/original.aspx" border=0 mce_src="http://blogs.msdn.com/photos/craigfr/images/2504516/original.aspx"&gt;&lt;/P&gt;
&lt;P&gt;Note that the capability to insert new "phantom" rows between locked rows that have already been scanned is the principle difference between the repeatable read and serializable isolation levels.&amp;nbsp; A serializable scan acquires a key range lock which prevents the insertion of any new rows anywhere within the range (as well as the update or deletion of any existing rows within the range).&lt;/P&gt;
&lt;P&gt;In the remainder of this post, I'll give a couple of examples of how we can get unexpected results even while running queries at repeatable read isolation level.&amp;nbsp; These examples are similar to the ones from my previous two posts.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;Row Movement&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;First, let's see how we can move a row and cause a repeatable read scan to miss it.&amp;nbsp; As with all of the other example in this series of posts, we'll need two sessions.&amp;nbsp; Begin by creating this simple table:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;create table t (a int primary key, b int)&lt;BR&gt;insert t values (1, 1)&lt;BR&gt;insert t values (2, 2)&lt;BR&gt;insert t values (3, 3)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Next, in session 1 lock the second row:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;begin tran&lt;BR&gt;update t set b = 2 where a = 2&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Now, in session 2 run a repeatable read scan of the table:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;select * from t with (repeatableread)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P mce_keep="true"&gt;This scan reads the first row then blocks waiting for session 1 to release the lock it holds on the second row.&amp;nbsp; While the scan is blocked, in session 1 let's move the third row to the beginning of the table before committing the transaction and releasing the exclusive lock blocking session 2:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;update t set a = 0 where a = 3&lt;BR&gt;commit tran&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P mce_keep="true"&gt;As we expect, session 2 completely misses the third row and returns just two rows:&lt;/P&gt;&lt;PRE&gt;a           b           c
----------- ----------- -----------
1           1           1
2           2           2&lt;/PRE&gt;
&lt;P mce_keep="true"&gt;Note that if we change the experiment so that session 1 tries to touch the first row in the table, it will cause a deadlock with session 2 which holds a lock on this row.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;Phantom Rows&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;Let's also take a look at how phantom rows can cause unexpected results.&amp;nbsp; This experiment is similar to the nested loops join experiment from my previous post.&amp;nbsp; Begin by creating two tables:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;create table t1 (a1 int primary key, b1 int)&lt;BR&gt;insert t1 values (1, 9)&lt;BR&gt;insert t1 values (2, 9)&lt;/P&gt;
&lt;P mce_keep="true"&gt;create table t2 (a2 int primary key, b2 int)&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Now, in session 1 lock the second row of table t1:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;begin tran&lt;BR&gt;update t1 set a1 = 2 where a1 = 2&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P mce_keep="true"&gt;Next, in session 2 run the following outer join at repeatable read isolation level:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;set transaction isolation level repeatable read&lt;BR&gt;select * from t1 left outer join t2 on b1 = a2&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P mce_keep="true"&gt;The query plan for this join uses a nested loops join:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; |--Nested Loops(Left Outer Join, WHERE:([t1].[b1]=[t2].[a2]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Scan(OBJECT:([t1].[PK__t1]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Scan(OBJECT:([t2].[PK__t2]))&lt;/P&gt;
&lt;P&gt;This plan scans the first row from t1, tries to join it with t2, finds there are no matching rows, and outputs a null extended row.&amp;nbsp; It then blocks waiting for session 1 to release the lock on the second row of t1.&amp;nbsp; Finally, in session 1, insert a new row into t2 and release the lock:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;insert t2 values (9, 0)&lt;BR&gt;commit tran&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P mce_keep="true"&gt;Here is the output from the outer join:&lt;/P&gt;&lt;PRE&gt;a1          b1          a2          b2
----------- ----------- ----------- -----------
1           9           NULL        NULL
2           9           9           0&lt;/PRE&gt;
&lt;P mce_keep="true"&gt;Notice that we have both a null extended and a joined row for the same join key!&lt;/P&gt;
&lt;P&gt;&lt;B&gt;Summary&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;As I pointed out at the conclusion of my previous post, I want to emphasize that the above results are not incorrect but rather are a side effect of running at a reduced isolation level. &amp;nbsp;SQL Server guarantees that the committed data is consistent at all times.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;EM&gt;CLARIFICATION 8/26/2008: The above examples work as I originally described if they are executed in tempdb.&amp;nbsp; However, the SELECT statements in session 2 may not block as described if the examples are executed in other databases due to an optimization where SQL Server avoids acquiring read committed locks when it knows that no data has changed on a page.&amp;nbsp; If you encounter this problem, either run these examples in tempdb or change the UPDATE statements in session 1 so that they actually change the data in the updated row.&amp;nbsp; For instance, for the first example try "update t set b = 12 where a = 2".&lt;/EM&gt;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=2504802" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Isolation+Levels/default.aspx">Isolation Levels</category></item><item><title>Read Committed Isolation Level</title><link>http://blogs.msdn.com/craigfr/archive/2007/04/25/read-committed-isolation-level.aspx</link><pubDate>Wed, 25 Apr 2007 19:59:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:2274063</guid><dc:creator>craigfr</dc:creator><slash:comments>7</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/2274063.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=2274063</wfw:commentRss><description>&lt;P&gt;SQL Server 2000 supports four different isolation levels: read uncommitted (or nolock), read committed, repeatable read, and serializable.&amp;nbsp; SQL Server 2005 adds two new isolation levels: read committed snapshot and snapshot.&amp;nbsp; These isolation levels determine what locks SQL Server takes when accessing data and, therefore, by extension they determine the level of concurrency and consistency that statements and transactions experience.&amp;nbsp; All of these isolation levels are described in &lt;A class="" title="SET TRANSACTION ISOLATION LEVEL (Transact-SQL)" href="http://msdn2.microsoft.com/en-us/library/ms173763.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms173763.aspx"&gt;Books Online&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;In this post, I'm going to take a closer look at the default isolation level of read committed.&amp;nbsp; When SQL Server executes a statement at the read committed isolation level, it acquires short lived share locks on a row by row basis.&amp;nbsp; The duration of these share locks is just long enough to read and process each row; the server generally releases each lock before proceeding to the next row.&amp;nbsp; Thus, if you run a simple select statement under read committed and check for locks (e.g., with sys.dm_tran_locks), you will typically see at most a single row lock at a time.&amp;nbsp; The sole purpose of these locks is to ensure that the statement only reads and returns committed data.&amp;nbsp; The locks work because updates always acquire an exclusive lock which blocks any readers trying to acquire a share lock.&lt;/P&gt;
&lt;P&gt;Now, let's suppose that we scan an entire table at read committed isolation level.&amp;nbsp; Since the scan locks only one row at a time, there is nothing to prevent a concurrent update from moving a row before or after our scan reaches it.&amp;nbsp; The following graphic illustrates this point:&lt;/P&gt;
&lt;P mce_keep="true"&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/2273874/original.aspx" border=0&gt;&lt;/P&gt;
&lt;P&gt;Let's try an experiment to see this effect in action.&amp;nbsp; We'll need two server sessions for this experiment.&amp;nbsp; First, create a simple table with three rows:&lt;/P&gt;
&lt;P&gt;create table t (a int primary key, b int)&lt;BR&gt;insert t values (1, 1)&lt;BR&gt;insert t values (2, 2)&lt;BR&gt;insert t values (3, 3)&lt;/P&gt;
&lt;P mce_keep="true"&gt;Next, in session 1 lock the second row:&lt;/P&gt;
&lt;P&gt;begin tran&lt;BR&gt;update t set b = 2 where a = 2&lt;/P&gt;
&lt;P&gt;Now, in session 2 run a simple scan of the table:&lt;/P&gt;
&lt;P&gt;select * from t&lt;/P&gt;
&lt;P mce_keep="true"&gt;This scan will read the first row and then block waiting for session 1 to release the lock it holds on the second row.&amp;nbsp; While the scan is blocked, in session 1 we can swap the first and third rows and then commit the transaction and release the exclusive lock blocking session 2:&lt;/P&gt;
&lt;P&gt;update t set a = 4 where a = 1&lt;BR&gt;update t set a = 0 where a = 3&lt;BR&gt;select * from t&lt;BR&gt;commit tran&lt;/P&gt;
&lt;P mce_keep="true"&gt;Here are the new contents of the table following these updates:&lt;/P&gt;
&lt;P&gt;a&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; b&lt;BR&gt;----------- -----------&lt;BR&gt;0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3&lt;BR&gt;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&lt;BR&gt;4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&lt;/P&gt;
&lt;P&gt;Finally, here is the result of the scan from session 2:&lt;/P&gt;
&lt;P&gt;a&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; b&lt;BR&gt;----------- -----------&lt;BR&gt;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;1&lt;BR&gt;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&lt;BR&gt;4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&lt;/P&gt;
&lt;P&gt;Notice that in this output the first row was scanned prior to the updates while the third row was scanned following the updates.&amp;nbsp; In fact, these two rows are really the same row from before and after the update.&amp;nbsp; Moreover, the original third row that had the value (3, 3) is not output at all.&amp;nbsp; (We could claim that changing the primary key effectively deleted one row and created a new row, but we could also achieve the same effect on a non-clustered index.)&lt;/P&gt;
&lt;P&gt;Finally, try repeating this experiment, but add a unique column to the table:&lt;/P&gt;
&lt;P&gt;create table t (a int primary key, b int, c int unique)&lt;BR&gt;insert t values (1, 1, 1)&lt;BR&gt;insert t values (2, 2, 2)&lt;BR&gt;insert t values (3, 3, 3)&lt;/P&gt;
&lt;P mce_keep="true"&gt;You'll get the same result, but you'll see "duplicates" in the unique column.&lt;/P&gt;
&lt;P&gt;If the above results are not acceptable, you can either enable read committed snapshot for your database or you can run at a higher isolation level (albeit with somewhat lower concurrency).&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;&lt;/P&gt;
&lt;P&gt;CLARIFICATION 8/26/2008: The above example works as I originally described if it is executed in tempdb.&amp;nbsp; However, the SELECT statement in session 2 may not block as described if the example is executed in other databases due to an optimization where SQL Server avoids acquiring read committed locks when it knows that no data has changed on a page.&amp;nbsp; If you encounter this problem, either run this example in tempdb or change the UPDATE statement in session 1 so that it actually changes the value of column b.&amp;nbsp; For example, try "update t set b = 12 where a = 2".&lt;/P&gt;&lt;/EM&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=2274063" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Isolation+Levels/default.aspx">Isolation Levels</category></item><item><title>Introduction to Partitioned Tables</title><link>http://blogs.msdn.com/craigfr/archive/2006/11/27/introduction-to-partitioned-tables.aspx</link><pubDate>Mon, 27 Nov 2006 22:56:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:1161061</guid><dc:creator>craigfr</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/1161061.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=1161061</wfw:commentRss><description>&lt;P&gt;In this post, I’m going to take a look at how query plans involving partitioned tables work.&amp;nbsp; Note that there is a big difference between partitioned tables (available only in SQL Server 2005) and partitioned views (available both in SQL Server 2000 and in SQL Server 2005).&amp;nbsp; I will look at the query plans for partitioned views in a future post.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Table Scan&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Let’s begin by creating a simple partitioned table:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;partition&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;function&lt;/SPAN&gt; pf&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;)&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;as&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;range&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;for&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;values&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;0&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 10&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 100&lt;SPAN style="COLOR: gray"&gt;)&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;partition&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;scheme&lt;/SPAN&gt; ps &lt;SPAN style="COLOR: blue"&gt;as&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;partition&lt;/SPAN&gt; pf &lt;SPAN style="COLOR: gray"&gt;all&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;to&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;[primary]&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;table&lt;/SPAN&gt; t &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;)&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; ps&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;)&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;This creates a table with four partitions.&amp;nbsp; SQL Server assigns sequential partition ids to each of the four partitions as follows:&lt;/P&gt;
&lt;P&gt;
&lt;TABLE class="" border=1&gt;
&lt;THEAD&gt;
&lt;TR&gt;
&lt;TH class=""&gt;PtnId&lt;/TH&gt;
&lt;TH class=""&gt;Values&lt;/TH&gt;&lt;/TR&gt;&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD class=""&gt;1&lt;/TD&gt;
&lt;TD class=""&gt;t.a &amp;lt;= 0&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class=""&gt;2&lt;/TD&gt;
&lt;TD class=""&gt;0 &amp;lt; t.a &amp;lt;= 10&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class=""&gt;3&lt;/TD&gt;
&lt;TD class=""&gt;10 &amp;lt; t.a &amp;lt;= 100&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD class=""&gt;4&lt;/TD&gt;
&lt;TD class=""&gt;100 &amp;lt; t.a&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P&gt;Now let’s examine the query plan for a simple table scan:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; t&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PARTITION ID:([PtnIds1004]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([t]))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;SQL Server explicitly enumerates the partition ids that the table scan must touch using the constant scan and nested loops join operators.&amp;nbsp; Recall that a nested loops join executes its second or inner input (in this case the table scan) once for each value from its first or outer input (in this case the constant scan).&amp;nbsp; Thus, we run the table scan four times; once for each partition id.&lt;/P&gt;
&lt;P&gt;Note also that the nested loops join explicitly identifies the partition id column as [PtnIds1004].&amp;nbsp; Although it is not immediately obvious from the text showplan (unfortunately, we omit this information in some though not all cases), the table scan uses the partition id column and checks it on each execution to determine which partition to scan.&amp;nbsp; This information is always available in the graphical showplan (via the table scan tool tip and properties) and in the XML showplan:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;TableScan&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Ordered&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;0&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;ForcedIndex&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;0&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;NoExpandHint&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;0&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;...&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Object&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Database&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;[master]&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;Schema&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;[dbo]&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;Table&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;[t]&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;PartitionId&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;ColumnReference&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Column&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;PtnIds1004&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;/&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;PartitionId&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;lt;/&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;TableScan&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Static Partition Elimination&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Consider the following query:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; t &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 100&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1005]) PARTITION ID:([PtnIds1005]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan(VALUES:(((1)),((2)),((3))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([t]), WHERE:([t].[a]&amp;lt;(100)) PARTITION ID:([PtnIds1005]))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The predicate “a &amp;lt; 100” clearly eliminates all rows from partition id 4.&amp;nbsp; There is no point in scanning this partition as none of the rows in this partition can possibly qualify for the predicate.&amp;nbsp; The optimizer recognizes this fact and eliminates that partition from the plan.&amp;nbsp; The constant scan now enumerates only three partitions.&amp;nbsp; We refer to this as “static partition elimination” because we know the exact list of partitions to scan statically at compile time.&lt;/P&gt;
&lt;P&gt;If we can statically eliminate all but one partition, we do not need the constant scan or join at all:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; t &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Table Scan(OBJECT:([t]), WHERE:([t].[a]&amp;lt;(0)) PARTITION ID:((1)))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Note that the exact partition id that we must scan (partition id 1) is now part of the table scan itself.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Dynamic Partition Elimination&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;In some cases, even though it is not possible for SQL Server to determine statically at compile time which partitions need to be scanned, the optimizer may be able to determine that some partition elimination is still possible.&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;declare&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: blue"&gt;int&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; t &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; @i&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PARTITION ID:([PtnIds1004]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Filter(WHERE:([PtnIds1004]&amp;lt;=RangePartitionNew([@i],(0),(0),(10),(100))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([t]), WHERE:([t].[a]&amp;lt;[@i]) PARTITION ID:([PtnIds1004]))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The above query is parameterized.&amp;nbsp; Since we do not get the actual parameter value until run time (that I’ve provided a constant value for the parameter in the batch does not change things), there is no way to determine at compile time which partition ids to scan.&amp;nbsp; We may scan just partition id 1, or we might scan partition ids 1 and 2, and so forth.&amp;nbsp; Thus, the constant scan enumerates all four partition ids and we use a filter to eliminate partitions ids at run time.&amp;nbsp; We refer to this as “dynamic partition elimination.”&lt;/P&gt;
&lt;P&gt;The filter compares each partition id to the results of a special function “RangePartitionNew.”&amp;nbsp; This function computes the results of applying the partition function to the parameter value.&amp;nbsp; The arguments to this function (in left to right order) are: the value (in this case the parameter @i) that we want to map to a partition id, a Boolean flag that indicates whether the partition function maps boundary values to the left (0) or right (1), and the actual partition boundary values (in this case 0, 10, and 100).&lt;/P&gt;
&lt;P&gt;In this example, since @i has the value 0, the result of RangePartitionNew is 1.&amp;nbsp; Thus, we scan only partition id 1.&amp;nbsp; Note that unlike the static partition elimination example, even though we scan only a single partition, we still have the constant scan and join.&amp;nbsp; Again, we need these operators since we do not know how many partitions we will scan until run time.&lt;/P&gt;
&lt;P&gt;In some cases, the optimizer can determine at compile time that we will scan only one partition even though it cannot determine which partition.&amp;nbsp; For example, if we have an equality predicate on the partition key, we know that only partition can match.&amp;nbsp; In this case, even though we have dynamic partition elimination, we do not need the constant scan and join.&amp;nbsp; For example:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;declare&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: blue"&gt;int&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; t &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; @i&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Table Scan(OBJECT:([t]), WHERE:([t].[a]=[@i]) PARTITION ID:(RangePartitionNew([@i],(0),(0),(10),(100))))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Combining Static and Dynamic Partition Elimination&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;SQL Server can combine static and dynamic partition elimination in a single query plan:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;declare&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: blue"&gt;int&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; t &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;&amp;gt;&lt;/SPAN&gt; 0 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; @i&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1004]) PARTITION ID:([PtnIds1004]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Filter(WHERE:([PtnIds1004]&amp;lt;=RangePartitionNew([@i],(0),(0),(10),(100))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan(VALUES:(((2)),((3)),((4))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([t]), WHERE:([t].[a]&amp;lt;[@i] AND [t].[a]&amp;gt;(0)) PARTITION ID:([PtnIds1004]))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Note that we statically eliminated partition id 1 from the constant scan and we may dynamically eliminate additional partition ids via a filter.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;$partition&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;You can explicitly invoke the RangePartitionNew function using $partition:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*,&lt;/SPAN&gt; &lt;SPAN style="COLOR: fuchsia"&gt;$partition&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;.&lt;/SPAN&gt;pf&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;)&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; t&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Compute Scalar(DEFINE:([Expr1004]=RangePartitionNew([t].[a],(0),(0),(10),(100))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([PtnIds1005]) PARTITION ID:([PtnIds1005]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Constant Scan(VALUES:(((1)),((2)),((3)),((4))))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([t]))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;This query plan is identical to the table scan plan above except that we compute and output the partition id for each row.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;For More Information&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;A class="" href="http://blogs.msdn.com/sqlcat/archive/2006/02/17/Partition-Elimination-in-SQL-Server-2005.aspx" mce_href="http://blogs.msdn.com/sqlcat/archive/2006/02/17/Partition-Elimination-in-SQL-Server-2005.aspx"&gt;This post&lt;/A&gt; from the SQL Server Development Customer Advisory Team blog includes more partition elimination examples.&amp;nbsp; The post includes some examples of where partition elimination does not work if you have predicates with disjunctions and parameters (such as “a &amp;gt; 0 or a &amp;lt; @i”) and some workarounds.&amp;nbsp; One of the workarounds is to use a “union all” statement to eliminate the disjunction.&amp;nbsp; If you use this workaround, beware that you must be sure that the or’ed predicates will not overlap and return duplicates.&amp;nbsp; In some cases, a duplicate eliminating union (be sure to include a unique key in the select list) may be safer.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=1161061" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Partitioned+Tables/default.aspx">Partitioned Tables</category></item><item><title>Parallel Scan</title><link>http://blogs.msdn.com/craigfr/archive/2006/11/01/parallel-scan.aspx</link><pubDate>Thu, 02 Nov 2006 07:16:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:928168</guid><dc:creator>craigfr</dc:creator><slash:comments>3</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/928168.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=928168</wfw:commentRss><description>&lt;P&gt;In this post, I’m going to take a look at how SQL Server parallelizes scans.&amp;nbsp; The scan operator is one of the few operators that is parallel “aware.”&amp;nbsp; Most operators neither need to know nor care whether they are executing in parallel; the scan is an exception.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;How does parallel scan work?&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The threads that compose a parallel scan work together to scan all of the rows in a table.&amp;nbsp; There is no a-priori assignment or rows or pages to a particular thread.&amp;nbsp; Instead, the storage engine dynamically hands out pages to threads.&amp;nbsp; A parallel page supplier coordinates access to the pages of the table.&amp;nbsp; The parallel page supplier ensures that each page is assigned to exactly one thread and, thus, is processed exactly once.&lt;/P&gt;
&lt;P&gt;At the beginning of a parallel scan, each thread requests a set of pages from the parallel page supplier.&amp;nbsp; The threads then begin processing these pages and returning rows.&amp;nbsp; When a thread finishes with its assigned set of pages, it requests the next set of pages from the parallel page supplier.&lt;/P&gt;
&lt;P&gt;This algorithm has a couple of advantages:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;It is independent of the number of threads.&amp;nbsp; We can add and remove threads from a parallel scan and it automatically adjusts.&amp;nbsp; If we double the number of threads, each thread processes (approximately) half as many pages.&amp;nbsp; And, if the I/O system can keep up, the scan runs twice as fast.&lt;/LI&gt;
&lt;LI&gt;It is resilient to skew or load imbalances.&amp;nbsp; If one thread runs slower than the other threads, that thread simply requests fewer pages while the other faster threads pick up the extra work.&amp;nbsp; The total execution time degrades smoothly.&amp;nbsp; (Compare this scenario to what would happen if we statically assigned pages to threads: the slow thread would dominate the total execution time.)&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;&lt;STRONG&gt;Example&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Let’s begin with a simple example.&amp;nbsp; To get a parallel plan, we’ll need a fairly big table; if the table is too small, the optimizer will conclude that a serial plan is perfectly adequate.&amp;nbsp; The following script creates a table with 1,000,000 rows and (thanks to the fixed length char(200) column) about 27,000 pages.&amp;nbsp; Warning: If you decide to run this example, it could a few minutes to populate this table.&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;table&lt;/SPAN&gt; T &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; x &lt;SPAN style="COLOR: blue"&gt;char&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;200&lt;SPAN style="COLOR: gray"&gt;))&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: green; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;set&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;nocount&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;on&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;declare&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: blue"&gt;int&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;set&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;while&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 1000000&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;begin&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;insert&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;values&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;@i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;set&lt;/SPAN&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; @i &lt;SPAN style="COLOR: gray"&gt;+&lt;/SPAN&gt; 1&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;end&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;Now, for the simplest possible query:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Table Scan(OBJECT:([T]))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;We get a serial plan!&amp;nbsp; Why don’t we get a parallel plan?&amp;nbsp; Parallelism is really about speeding up queries by applying more CPUs to the problem.&amp;nbsp; The cost of this query is dominated by the cost of reading pages from disk (which is mitigated by read ahead rather than parallelism) and returning rows to the client.&amp;nbsp; The query uses relatively few CPU cycles and, in fact, would probably run slower if we parallelized it.&lt;/P&gt;
&lt;P&gt;If we add a fairly selective predicate to the query, we can get a parallel plan:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 1000&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Parallelism(Gather Streams)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([T]), WHERE:([T].[a]&amp;lt;CONVERT_IMPLICIT(int,[@1],0)))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;By running this query in parallel, we can distribute the cost of evaluating this predicate across multiple CPUs.&amp;nbsp; (In this case, the predicate is so cheap that it probably does not make much difference whether or not we run in parallel.)&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Load Balancing&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;As I mentioned above, the parallel scan algorithm dynamically allocates pages to threads.&amp;nbsp; We can see this in action.&amp;nbsp; Consider this query which returns every row of the table:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;%&lt;/SPAN&gt; 2 &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;%&lt;/SPAN&gt; 2 &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;The peculiar predicate confuses the optimizer which underestimates the cardinality and generates a parallel plan:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Parallelism(Gather Streams)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Table Scan(OBJECT:([T]), WHERE:([T].[a]%(2)=(0) OR [T].[a]%(2)=(1)))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;On SQL Server 2005, using “SET STATISTICS XML ON” we can see exactly how many rows each thread processes.&amp;nbsp; Here is an excerpt of the XML output on a two processor system:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RelOp&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;NodeId&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;2&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;PhysicalOp&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;Table Scan&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;LogicalOp&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;Table Scan&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ...&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeInformation&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeCountersPerThread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Thread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;2&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;ActualRows&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;530432&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ... /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeCountersPerThread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Thread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;1&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;ActualRows&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;469568&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ... /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeCountersPerThread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Thread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;0&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;ActualRows&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;0&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ... /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;/&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeInformation&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;...&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;lt;/&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RelOp&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;We can see that both threads (threads 1 and 2) processed approximately half of the rows.&amp;nbsp; (Thread 0 is the coordinator or main thread.&amp;nbsp; It only executes the portion of the query plan above the topmost exchange.&amp;nbsp; Thus, we do not expect it to process any rows for any parallel operators.)&lt;/P&gt;
&lt;P&gt;Now let’s repeat the experiment, but let’s run an expensive serial query at the same time.&amp;nbsp; This cross join query will run for a really long time (it needs to process one trillion rows) and use plenty of CPU cycles:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: fuchsia"&gt;min&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;T1&lt;SPAN style="COLOR: gray"&gt;.&lt;/SPAN&gt;a &lt;SPAN style="COLOR: gray"&gt;+&lt;/SPAN&gt; T2&lt;SPAN style="COLOR: gray"&gt;.&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;)&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T T1 &lt;SPAN style="COLOR: gray"&gt;cross&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;join&lt;/SPAN&gt; T T2 &lt;SPAN style="COLOR: blue"&gt;option&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;maxdop 1&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;This serial query will consume cycles from only one of the two CPUs.&amp;nbsp; While it is running, let’s run the other query again:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;%&lt;/SPAN&gt; 2 &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;%&lt;/SPAN&gt; 2 &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RelOp&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;NodeId&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;2&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;PhysicalOp&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;Table Scan&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;LogicalOp&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;Table Scan&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ...&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeInformation&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeCountersPerThread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Thread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;1&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;ActualRows&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;924224&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ... /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeCountersPerThread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Thread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;2&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;ActualRows&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;75776&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ... /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeCountersPerThread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: red; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;Thread&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;=&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;"&lt;SPAN style="COLOR: blue"&gt;0&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; &lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;ActualRows&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;=&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt;0&lt;/SPAN&gt;"&lt;SPAN style="COLOR: blue"&gt; ... /&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&amp;lt;/&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RunTimeInformation&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;...&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;lt;/&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: maroon; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;RelOp&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&amp;gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;This time thread 1 processed more than 90% of the rows while thread 2 which was busy executing the above serial plan processed far fewer rows.&amp;nbsp; The parallel scan automatically balanced the work across the two threads.&amp;nbsp; Since thread 1 had more free cycles (it wasn’t competing with the serial plan), it requested and scanned more pages.&lt;/P&gt;
&lt;P&gt;If you try this experiment, don’t forget to kill the serial query when you are done!&amp;nbsp; Otherwise, it will continue to run and waste cycles for a really long time.&lt;/P&gt;
&lt;P&gt;The same load balancing that we just observed applies equally whether a thread is slowed down because of an external factor (such as the serial query in this example) or because of an internal factor.&amp;nbsp; For example, if it costs more to process some rows than others, we will see the same behavior.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=928168" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category><category domain="http://blogs.msdn.com/craigfr/archive/tags/Parallelism/default.aspx">Parallelism</category></item><item><title>Index Union</title><link>http://blogs.msdn.com/craigfr/archive/2006/10/18/index-union.aspx</link><pubDate>Wed, 18 Oct 2006 19:54:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:839457</guid><dc:creator>craigfr</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/839457.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=839457</wfw:commentRss><description>&lt;P&gt;I was planning to continue writing about parallelism this week (and I will continue another time in another post), but I received an interesting question and chose to write about it instead.&lt;/P&gt;
&lt;P&gt;Let’s begin by considering the following query:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;table&lt;/SPAN&gt; T &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; x &lt;SPAN style="COLOR: blue"&gt;char&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;200&lt;SPAN style="COLOR: gray"&gt;))&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;unique&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;clustered&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; Ta &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; Tb &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;b&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; Tc &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;c&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: gray; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;insert&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; T &lt;SPAN style="COLOR: blue"&gt;values&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;1&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 1&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 1&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 1&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;insert&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; T &lt;SPAN style="COLOR: blue"&gt;values&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;2&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 2&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 2&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 2&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;insert&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; T &lt;SPAN style="COLOR: blue"&gt;values&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;3&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 3&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 3&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; 3&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: gray; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(1) OR [T].[b]=(3)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;We have an index on column b and not surprisingly, the optimizer chooses to use the index and perform a seek.&amp;nbsp; Since we have two predicates on column b, we get a seek with two predicates.&amp;nbsp; First we execute the predicate “b = 1” and then we execute the predicate “b = 3”.&amp;nbsp; Note that since we only output column a and since column a is the clustering key (and is, thus, covered by all non-clustered indexes), there is no need for a bookmark lookup.&amp;nbsp; So far, there are no surprises.&lt;/P&gt;
&lt;P&gt;Note that we could just as easily write this query as:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;union&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: gray"&gt;all&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Concatenation&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(1)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(3)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;The optimizer does not collapse the union all into a single index seek, but the queries and the plans are logically identical.&lt;/P&gt;
&lt;P&gt;Now consider this query:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Clustered Index Scan(OBJECT:([T].[Ta]), WHERE:([T].[b]=(1) OR [T].[c]&amp;lt;(3)))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;We have an index on both columns b and c, but we didn’t use either.&amp;nbsp; Why not?&amp;nbsp; We need all rows that satisfy either predicate.&amp;nbsp; We could use the index on column b to get rows that satisfy the predicate “b = 1”, but we may miss rows that satisfy “c &amp;lt; 3” but for which “b != 1”.&amp;nbsp; For example, we would miss the row with the value (2, 2, 2, 2).&amp;nbsp; The same problem would occur if we use the index on column c to satisfy the predicate “c &amp;lt; 3”.&amp;nbsp; (My sample data does not include a row with “b = 1” for which “c &amp;gt;= 3” but such a row could exist and so we must assume one does.)&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Index union&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;So, is there a way for SQL Server to evaluate this query and use the indexes?&amp;nbsp; Yes!&amp;nbsp; First, for the optimizer to choose a plan other than the clustered index scan, we need to add enough data to the table to make the scan more expensive.&amp;nbsp; (Note that I added a char(200) column to the original table to make the rows larger.&amp;nbsp; This column makes the table occupy more pages which also makes the scan more expensive.)&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;truncate&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;table&lt;/SPAN&gt; T&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;set&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;nocount&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;on&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;declare&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: blue"&gt;int&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;set&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;while&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 1000&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;begin&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;insert&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;values&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;@i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;set&lt;/SPAN&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; @i &lt;SPAN style="COLOR: gray"&gt;+&lt;/SPAN&gt; 1&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;end&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Sort(DISTINCT ORDER BY:([T].[a] ASC))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Concatenation&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(1)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tc]), SEEK:([T].[c] &amp;lt; (3)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;This plan is very similar to the union all plan that we saw above.&amp;nbsp; The optimizer rewrote the query as:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;union&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;Note, however, that since the two queries that make up the union could return duplicate rows, we must use a union (which eliminates duplicates) rather than a union all (which does not).&amp;nbsp; The concatenation operator implements union all while the sort distinct eliminates duplicates and turns the union all into a union.&amp;nbsp; We refer to this type of plan as an “index union.”&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Merge union&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Now let’s change the query slightly:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; c &lt;B style="mso-bidi-font-weight: normal"&gt;&lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt;&lt;/B&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Stream Aggregate(GROUP BY:([T].[a]))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Merge Join(Concatenation)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(1)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tc]), SEEK:([T].[c]=(3)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;Instead of the concatenation and sort distinct operators, we now have a merge join (concatenation) and a stream aggregate.&amp;nbsp; What happened?&amp;nbsp; The merge join (concatenation) or “merge union” is not really a join at all.&amp;nbsp; It is implemented by the same iterator as the merge join, but it really performs a union all while preserving the order of the input rows.&amp;nbsp; Finally, we use the stream aggregate to eliminate duplicates.&amp;nbsp; (See &lt;A class="" href="http://blogs.msdn.com/craigfr/archive/2006/09/13/752728.aspx" mce_href="http://blogs.msdn.com/craigfr/archive/2006/09/13/752728.aspx"&gt;this post&lt;/A&gt; for more about using stream aggregate to eliminate duplicates.)&amp;nbsp; This plan is generally a better choice since the sort distinct uses memory and could spill data to disk if it runs out of memory while the stream aggregate does not use memory.&lt;/P&gt;
&lt;P&gt;So, why didn’t we use this plan before?&amp;nbsp; Just like a merge join, the merge union requires that input data be sorted on the merge key (in this case column a).&amp;nbsp; The non-clustered index Tb covers the explicit index key b and the clustering key a.&amp;nbsp; Thus, this index returns rows in the order (b, a).&amp;nbsp; However, with an equality predicate such as “b = 1”, column b is a constant so we actually get rows ordered by column a.&amp;nbsp; The same thing happens with the index Tc and the predicate “c = 3”.&amp;nbsp; So, we have two inputs that are both ordered by column a and we can use the merge union.&lt;/P&gt;
&lt;P&gt;In the prior example, one of the predicates was “c &amp;lt; 3”.&amp;nbsp; Because this predicate is an inequality, the index seek returns rows in the order (c, a).&amp;nbsp; Since the rows are not strictly sorted by a, we cannot use the merge union.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Union of three indexes&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The concatenation operator directly supports more than two inputs:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 2 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Sort(DISTINCT ORDER BY:([T].[a] ASC))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Concatenation&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Clustered Index Seek(OBJECT:([T].[Ta]), SEEK:([T].[a]=(1)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(2)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tc]), SEEK:([T].[c] &amp;lt; (3)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;Merge union only supports two inputs, but it can be cascaded to handle more than two inputs:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 2 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Stream Aggregate(GROUP BY:([T].[a]))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Merge Join(Concatenation)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Merge Join(Concatenation)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Clustered Index Seek(OBJECT:([T].[Ta]), SEEK:([T].[a]=(1)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(2)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tc]), SEEK:([T].[c]=(3)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;What columns does the union return?&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;A union only returns the columns that are common to all of its inputs.&amp;nbsp; In all of the above index union examples, the only column that the indexes have in common is the clustering key a.&amp;nbsp; (Recall that index Tb covers (b, a) while index Tc covers (c, a).)&amp;nbsp; Thus, the union can only return column a.&amp;nbsp; If we ask for other columns, we must perform a bookmark lookup.&amp;nbsp; This is true even if one of the indexes in the union covers the extra columns.&amp;nbsp; For example, if we ask for all three columns a, b, and c, we get a bookmark lookup even though column b is covered by Tb and column c is covered by Tc:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 1 &lt;SPAN style="COLOR: gray"&gt;or&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 3&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&lt;FONT face="Times New Roman" size=3&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;|--Nested Loops(Inner Join, OUTER REFERENCES:([T].[a]))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Stream Aggregate(GROUP BY:([T].[a]))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Merge Join(Concatenation)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(1)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Index Seek(OBJECT:([T].[Tc]), SEEK:([T].[c]=(3)) ORDERED FORWARD)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN style="FONT-SIZE: 8pt; FONT-FAMILY: Tahoma"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;|--Clustered Index Seek(OBJECT:([T].[Ta]), SEEK:([T].[a]=[T].[a]) LOOKUP ORDERED FORWARD)&lt;/SPAN&gt;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=839457" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category></item><item><title>Index Examples and Tradeoffs</title><link>http://blogs.msdn.com/craigfr/archive/2006/07/13/664902.aspx</link><pubDate>Thu, 13 Jul 2006 23:12:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:664902</guid><dc:creator>craigfr</dc:creator><slash:comments>3</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/664902.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=664902</wfw:commentRss><description>&lt;P&gt;The optimizer must choose an appropriate “access path” to read data from each table referenced in a query.&amp;nbsp; The optimizer considers many factors when deciding which index to use, whether to do a scan or a seek, and whether to do a bookmark lookup.&amp;nbsp; These factors include:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;How many I/Os will a seek or scan of the index perform?&lt;/LI&gt;
&lt;LI&gt;Are the keys of the index suitable for evaluating a predicate in the query?&lt;/LI&gt;
&lt;LI&gt;How selective is the predicate?&amp;nbsp; (That is, what percentage of the total rows in the table qualifies for this predicate?&amp;nbsp; The lower this number the better.)&lt;/LI&gt;
&lt;LI&gt;Does the index cover all of the necessary columns?&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;In this post, I’m going to give some examples of how these factors interact.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Schema&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;I’ll use this schema for all of the following examples:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;table&lt;/SPAN&gt; T &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; d &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; x &lt;SPAN style="COLOR: blue"&gt;char&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;200&lt;SPAN style="COLOR: gray"&gt;))&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;unique&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;clustered&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; Ta &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; Tb &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;b&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; Tcd &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;c&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; d&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; Tdc &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;d&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;
&lt;P&gt;If you want to try the examples, I populated the table using the following script:&lt;/P&gt;
&lt;P&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;set&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;nocount&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;on&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;declare&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: blue"&gt;int&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;set&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 0&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;while&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; @i &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 100000&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;begin&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;insert&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;values&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;@i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; @i&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;set&lt;/SPAN&gt; @i &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; @i &lt;SPAN style="COLOR: gray"&gt;+&lt;/SPAN&gt; 1&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;end&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;
&lt;P&gt;&lt;STRONG&gt;I/O example&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Consider this query:&lt;/P&gt;
&lt;P&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;This query has no WHERE clause so we must use a scan.&amp;nbsp; However, there are two indexes we can scan.&amp;nbsp; There is the clustered index Ta and there is the non-clustered index Tb.&amp;nbsp; Both of these indexes cover columns a and b.&amp;nbsp; However, the clustered index also covers columns c and x.&amp;nbsp; Because column x is a char(200), the total width of each row in the clustered index is over 200 bytes, fewer than 40 rows fit on each 8K page, and the index requires more than 2,500 pages to store all 100,000 rows.&amp;nbsp; In contrast, the total width of each row in the non-clustered index, is only 8 bytes plus some overhead, hundreds of rows fit on each page, and the index requires fewer than 250 pages to store all 100,000 rows.&amp;nbsp; By scanning the non-clustered index, we can execute the query while performing many fewer I/Os.&lt;/P&gt;
&lt;P&gt;Thus, the better plan is:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Index Scan(OBJECT:([T].[Tb]))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Note that we can use sys.dm_db_index_physical_stats to compare the indexes:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; index_id&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; page_count&lt;SPAN style="COLOR: gray"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;from&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; sys.dm_db_index_physical_stats&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="mso-spacerun: yes"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: fuchsia"&gt;DB_ID&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;'tempdb'&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;),&lt;/SPAN&gt; &lt;SPAN style="COLOR: fuchsia"&gt;OBJECT_ID&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: red"&gt;'T'&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;),&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;NULL,&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;NULL,&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;NULL)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=2&gt;index_id&amp;nbsp;&amp;nbsp;&amp;nbsp; page_count&lt;BR&gt;----------- --------------------&lt;BR&gt;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2858&lt;BR&gt;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 174&lt;BR&gt;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 223&lt;BR&gt;4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 223&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;We can also use stats I/O and index hints to compare the number of I/Os for the two possible plans:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;set&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;statistics&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;io&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;with&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;Ta&lt;SPAN style="COLOR: gray"&gt;))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;Table 'T'. Scan count 1, logical reads &lt;FONT color=#ff0000&gt;2872&lt;/FONT&gt;, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;with&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;Tb&lt;SPAN style="COLOR: gray"&gt;))&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;Table 'T'. Scan count 1, logical reads &lt;FONT color=#ff0000&gt;176&lt;/FONT&gt;, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Selectivity example&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Consider this query:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;&amp;gt;&lt;/SPAN&gt; 150 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; c &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 160 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; d &lt;SPAN style="COLOR: gray"&gt;&amp;gt;&lt;/SPAN&gt; 100 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; d &lt;SPAN style="COLOR: gray"&gt;&amp;lt;&lt;/SPAN&gt; 200&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;This query has two different predicates that we might use for an index seek.&amp;nbsp; We can use the predicate on column c with the non-clustered index Tcd or we can use the predicate on column d with the non-clustered index Tdc.&amp;nbsp; (Refer to &lt;A href="http://blogs.msdn.com/craigfr/archive/2006/07/07/652668.aspx"&gt;my post on seek predicates&lt;/A&gt;&amp;nbsp;for an explanation of why we cannot use a single index to satisfy both inequality predicates.)&lt;/P&gt;
&lt;P&gt;The optimizer looks at the selectivity of the two predicates to determine which index to use.&amp;nbsp; The predicate on column c selects only 9 rows while the predicate on column d selects 99 rows.&amp;nbsp; Thus, it is cheaper to seek using the index Tcd and evaluate a residual predicate on column d for 9 rows than it is to seek using the index Tdc and evaluate a residual predicate on column c for 99 rows. &lt;/P&gt;
&lt;P&gt;Here is the plan:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Index Seek(OBJECT:([T].[Tcd]), SEEK:([T].[c] &amp;gt; (150) AND [T].[c] &amp;lt; (160)),&amp;nbsp; WHERE:([T].[d]&amp;gt;(100) AND [T].[d]&amp;lt;(200)) ORDERED FORWARD)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Seek vs. scan example&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Consider these two queries:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;between&lt;/SPAN&gt; 1001 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; 9000&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; a &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; a &lt;SPAN style="COLOR: gray"&gt;between&lt;/SPAN&gt; 101 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; 90000&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;As you might expect, for the first query, the optimizer chooses a clustered index seek to satisfy the predicate on column a.&amp;nbsp; Here is the plan:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Clustered Index Seek(OBJECT:([T].[Ta]), SEEK:([T].[a] &amp;gt;= CONVERT_IMPLICIT(int,[@1],0) AND [T].[a] &amp;lt;= CONVERT_IMPLICIT(int,[@2],0)) ORDERED FORWARD)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;(Note that the parameters in this plan are due to auto-parameterization.&amp;nbsp; When we execute this plan, @1 has the value 1001 and @2 has the value 9000.)&lt;/P&gt;
&lt;P&gt;For the second query, instead of the clustered index seek, the optimizer chooses an index scan of the non-clustered index Tb and uses a residual predicate for the WHERE clause.&amp;nbsp; Again, here is the plan:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Index Scan(OBJECT:([T].[Tb]),&amp;nbsp; WHERE:([T].[a]&amp;gt;=(101) AND [T].[a]&amp;lt;=(90000)))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;What happened?&amp;nbsp; The predicate on the first query selects 8,000 out of 100,000 rows; this is about 8% of the table or about 230 pages of the clustered index.&amp;nbsp; The predicate on the second query selects 89,000 rows; this is nearly 90% of the table and if we were to use the clustered index it would mean touching over 2,500 pages.&amp;nbsp; By comparison, we can scan the entire non-clustered index Tb and touch only 174 pages.&amp;nbsp; Thus, the optimizer chooses the plan that requires significantly fewer I/Os. &lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Seek with bookmark lookup vs. scan example&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Consider these two queries:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; x &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;between&lt;/SPAN&gt; 101 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; 200&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; x &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;between&lt;/SPAN&gt; 1001 &lt;SPAN style="COLOR: gray"&gt;and&lt;/SPAN&gt; 2000&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;We again have two plans from which to choose.&amp;nbsp; We can scan the clustered index directly and apply the predicate on column b as a residual.&amp;nbsp; Or, we can use the non-clustered index Tb and perform a seek using the predicate on column b then do a bookmark lookup on the clustered index to get the value of column x for each qualifying row.&amp;nbsp; In &lt;A href="http://blogs.msdn.com/craigfr/archive/2006/06/30/652639.aspx"&gt;my bookmark lookup post&lt;/A&gt;, I explained that bookmark lookups perform random I/Os which are very expensive.&amp;nbsp; Thus, the plan with the bookmark lookup is only a good plan when the seek predicate is very selective.&lt;/P&gt;
&lt;P&gt;The first query touches only 100 rows and the optimizer concludes that the bookmark lookup is worthwhile:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([T].[a], [Expr1005]) ...)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b] &amp;gt;= (101) AND [T].[b] &amp;lt;= (200)) ...)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Seek(OBJECT:([T].[Ta]), SEEK:([T].[a]=[T].[a]) LOOKUP ...)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The second query touches 1,000 rows.&amp;nbsp; Although this is still only 1% of the table, the optimizer concludes that 1,000 random I/Os are more expensive than 2,800 sequential I/Os and opts for the clustered index scan:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Clustered Index Scan(OBJECT:([T].[Ta]), WHERE:([T].[b]&amp;gt;=(1001) AND [T].[b]&amp;lt;=(2000)))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Next up … Joins&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;There are still more topics and issues related to indexes, scans and seeks, and so forth, but I think it’s time to move on to something new so with my next post I’m going to start writing about joins.&amp;nbsp; As always, I’m interested to hear what you have to say.&amp;nbsp; If you have any comments or feedback, please let me know.&lt;BR&gt;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=664902" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category></item><item><title>Seek Predicates</title><link>http://blogs.msdn.com/craigfr/archive/2006/07/07/652668.aspx</link><pubDate>Fri, 07 Jul 2006 23:36:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:652668</guid><dc:creator>craigfr</dc:creator><slash:comments>5</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/652668.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=652668</wfw:commentRss><description>&lt;P&gt;Before SQL Server can perform an index seek, it must determine whether the keys of the index are suitable for evaluating a predicate in the query.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Single column indexes&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Single column indexes are fairly straightforward.&amp;nbsp; SQL Server can use single column indexes to answer most simple comparisons including equality and inequality (greater than, less than, etc.) comparisons.&amp;nbsp; More complex expressions such as functions over a column and “like” predicates with a leading wildcard character will generally prevent SQL Server from using an index seek.&lt;/P&gt;
&lt;P&gt;For example, suppose we have a single column index on a column “a.”&amp;nbsp; We can use this index to seek on these predicates:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;a = 3.14&lt;/LI&gt;
&lt;LI&gt;a &amp;gt; 100&lt;/LI&gt;
&lt;LI&gt;a between 0 and 99&lt;/LI&gt;
&lt;LI&gt;a like ‘abc%’&lt;/LI&gt;
&lt;LI&gt;a in (2, 3, 5, 7)&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;However, we cannot use it to seek on these predicates:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;ABS(a) = 1&lt;/LI&gt;
&lt;LI&gt;a + 1 = 9&lt;/LI&gt;
&lt;LI&gt;a like ‘%abc’&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;Multi-column indexes&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Multi-column indexes are slightly more complex.&amp;nbsp; With a multi-column index, the order of the keys matters.&amp;nbsp; It determines the sort order of the index and it affects the set of seek predicates that SQL Server can evaluate using the index.&lt;/P&gt;
&lt;P&gt;For an easy way to visualize why order matters, think about the phone book.&amp;nbsp; The phone book is like an index with the keys (last name, first name).&amp;nbsp; The contents of the phone book are sorted by last name and it is easy to look someone up if we know their last name.&amp;nbsp; However, if we have only a first name, it is very difficult to get a list of people with that name.&amp;nbsp; We would need another phone book sorted on first name.&lt;/P&gt;
&lt;P&gt;In the same way, if we have an index on two columns, we can only use the index to satisfy a predicate on the second column if we have an equality predicate on the first column.&amp;nbsp; Even if we cannot use the index to satisfy the predicate on the second column, we may be able to use it on the first column.&amp;nbsp; In this case, we introduce a residual predicate for the predicate on the second column.&amp;nbsp; This is the same residual predicate that we use for scans.&lt;/P&gt;
&lt;P&gt;For example, suppose we have a two column index on column “a” and “b.”&amp;nbsp; We can use this index to seek on any of the predicates that worked on the single column index.&amp;nbsp; We can also use it to seek on these additional predicates:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;a = 3.14 and b = ‘pi’&lt;/LI&gt;
&lt;LI&gt;a = ‘xyzzy’ and b &amp;lt;= 0&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;For the next set of examples, we can use the index to satisfy the predicate on column a, but not on column b.&amp;nbsp; In these cases, we need a residual predicate.&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;a &amp;gt; 100 and b &amp;gt; 100&lt;/LI&gt;
&lt;LI&gt;a like ‘abc%’ and b = 2&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;Finally, we cannot use the index to seek on the next set of predicates as we cannot seek even on column a.&amp;nbsp; In these cases, we must use a different index (e.g., one where column b is the leading column) or we must use a scan with a residual predicate.&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;b = 0&lt;/LI&gt;
&lt;LI&gt;a + 1 = 9 and b between 1 and 9&lt;/LI&gt;
&lt;LI&gt;a like ‘%abc’ and b in (1, 3, 5)&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;A slightly more concrete example&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Consider the following schema:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New"&gt;&lt;FONT size=1&gt;&lt;FONT color=#0000ff&gt;create table&lt;/FONT&gt; person (id &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, last_name &lt;FONT color=#0000ff&gt;varchar&lt;/FONT&gt;(30), first_name &lt;FONT color=#0000ff&gt;varchar&lt;/FONT&gt;(30))&lt;BR&gt;&lt;FONT color=#0000ff&gt;create unique clustered index&lt;/FONT&gt; person_id &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; person (id)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create index&lt;/FONT&gt; person_name &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; person (last_name, first_name)&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Here are three queries and their corresponding text showplan.&amp;nbsp; The first query seeks on both columns of the person_name index.&amp;nbsp; The second query seeks on the first column only and uses a residual predicate to evaluate first_name.&amp;nbsp; The third query cannot seek and uses a scan with a residual predicate.&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;select id from person where last_name = 'Doe' and first_name = 'John'&lt;BR&gt;&amp;nbsp; |--Index Seek(OBJECT:([person].[person_name]), SEEK:([person].[last_name]='Doe' AND [person].[first_name]='John'))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;select id from person where last_name &amp;gt; 'Doe' and first_name = 'John'&lt;BR&gt;&amp;nbsp; |--Index Seek(OBJECT:([person].[person_name]), SEEK:([person].[last_name] &amp;gt; 'Doe'),&amp;nbsp; WHERE:([person].[first_name]='John'))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;select id from person where last_name like '%oe' and first_name = 'John'&lt;BR&gt;&amp;nbsp; |--Index Scan(OBJECT:([person].[person_name]),&amp;nbsp; WHERE:([person].[first_name]='John' AND [person].[last_name] like '%oe'))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Note:&amp;nbsp; In case you are trying to reproduce these plans, in this and some of my prior examples, I’ve used index hints (which I haven’t shown) to ensure that I get the plan I want to illustrate without having to insert data into the table.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;A bit more about index keys&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;In most cases, the index keys are the set of columns that you specify in the create index statement.&amp;nbsp; However, when you create a non-unique non-clustered index on a table with a clustered index, we append the clustered index keys to the non-clustered index keys if they are not explicitly part of the non-clustered index keys.&amp;nbsp; You can seek on these implicit keys just as if you specified them explicitly.&lt;/P&gt;
&lt;P&gt;For example, given this schema:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;FONT color=#000000&gt;&lt;FONT size=1&gt;&lt;FONT color=#0000ff&gt;create table&lt;/FONT&gt; T_heap (a &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, b &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, c &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, d &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, e &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, f &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create index&lt;/FONT&gt; T_heap_a &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_heap (a)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create index&lt;/FONT&gt; T_heap_bc &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_heap (b, c)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create index&lt;/FONT&gt; T_heap_d &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_heap (d) &lt;FONT color=#0000ff&gt;include&lt;/FONT&gt; (e)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create unique index&lt;/FONT&gt; T_heap_f &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_heap (f)&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;FONT face="Courier New" color=#000000 size=1&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;FONT color=#000000&gt;&lt;FONT size=1&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;FONT color=#000000&gt;&lt;FONT color=#0000ff size=1&gt;create table &lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;T_clu (a &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, b &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, c &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, d &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, e &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;, f &lt;FONT color=#0000ff&gt;int&lt;/FONT&gt;)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create unique clustered index&lt;/FONT&gt; T_clu_a &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_clu (a)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create index&lt;/FONT&gt; T_clu_b &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_clu (b)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create index&lt;/FONT&gt; T_clu_ac &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_clu (a, c)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create index&lt;/FONT&gt; T_clu_d &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_clu (d) &lt;FONT color=#0000ff&gt;include&lt;/FONT&gt; (e)&lt;BR&gt;&lt;FONT color=#0000ff&gt;create unique index&lt;/FONT&gt; T_clu_f &lt;FONT color=#0000ff&gt;on&lt;/FONT&gt; T_clu (f)&lt;/FONT&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;/SPAN&gt;The key columns and covered columns for each index are:&lt;/P&gt;
&lt;P&gt;
&lt;TABLE class=MsoTableGrid style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; BORDER-COLLAPSE: collapse; mso-border-alt: solid windowtext .5pt; mso-padding-alt: 0in 5.4pt 0in 5.4pt; mso-border-insideh: .5pt solid windowtext; mso-border-insidev: .5pt solid windowtext; mso-yfti-tbllook: 480" cellSpacing=0 cellPadding=0 border=1&gt;
&lt;TBODY&gt;
&lt;TR style="mso-yfti-irow: 0; mso-yfti-firstrow: yes"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Index&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Key Columns&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Covered Columns&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 1"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_heap_a&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 2"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_heap_bc&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;b, c&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;b, c&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 3"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_heap_d&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;d&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;d, e&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 4"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_heap_f&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;f&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;f&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 5"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN lang=PT-BR style="mso-ansi-language: PT-BR"&gt;T_clu_a&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN lang=PT-BR style="mso-ansi-language: PT-BR"&gt;a&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN lang=PT-BR style="mso-ansi-language: PT-BR"&gt;a, b, c, d, e&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 6"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_clu_b&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;b, a&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, b&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 7"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_clu_ac&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, c&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, c&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 8"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_clu_d&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;d, a&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, d, e&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 9; mso-yfti-lastrow: yes"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_clu_f&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;f&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, f&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P&gt;Note that each of the non-clustered indexes on T_clu include the clustered index key column a with the exception of T_clu_f which is a unique index.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;To be continued one last time …&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;I’m planning one more post about scans and seeks.&amp;nbsp; I’ll write a bit more about some of the tradeoffs that SQL Server considers when deciding which index to use and whether to perform a scan, a seek, or a seek with a bookmark lookup.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=652668" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category></item><item><title>Bookmark Lookup</title><link>http://blogs.msdn.com/craigfr/archive/2006/06/30/652639.aspx</link><pubDate>Fri, 30 Jun 2006 23:05:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:652639</guid><dc:creator>craigfr</dc:creator><slash:comments>14</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/652639.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=652639</wfw:commentRss><description>&lt;P&gt;In my last post, I explained how SQL Server can use an index to efficiently locate rows that qualify for a predicate.&amp;nbsp; When deciding whether to use an index, SQL Server considers several factors.&amp;nbsp; These factors include checking whether the index covers all of the columns that the query references (for the table in question).&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;What does it mean for an index to cover a column?&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;The heap or clustered index for a table (often called the “base table”) contains (or covers) all columns in the table.&amp;nbsp; Non-clustered indexes, on the other hand, contain (or cover) only a subset of the columns in the table.&amp;nbsp; By limiting the set of columns stored in a non-clustered index, we can store more rows on each page.&amp;nbsp; Thus, we save disk space and improve the efficiency of seeks and scans by reducing the number of I/Os and the number of pages that we touch.&lt;/P&gt;
&lt;P&gt;Each non-clustered index covers the key columns that were specified when it was created.&amp;nbsp; Also, if the base table is a clustered index, each non-clustered index on this table covers the clustered index keys (often called the “clustering keys”) regardless of whether they are part of the non-clustered index’s key columns.&amp;nbsp; In SQL Server 2005, we can also add additional non-key columns to a non-clustered index using the “include” clause of the create index statement.&lt;/P&gt;
&lt;P&gt;For example, given this schema:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;table&lt;/SPAN&gt; T_heap &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; d &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; e &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;)&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; T_heap_a &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T_heap &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; T_heap_bc &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T_heap &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;b&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; T_heap_d &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T_heap &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;d&lt;SPAN style="COLOR: gray"&gt;)&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;e&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: gray; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;table&lt;/SPAN&gt; T_clu &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; b &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; d &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; e &lt;SPAN style="COLOR: blue"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;unique clustered&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; T_clu_a &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T_clu &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; T_clu_b &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T_clu &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;b&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; T_clu_ac &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T_clu &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;a&lt;SPAN style="COLOR: gray"&gt;,&lt;/SPAN&gt; c&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;create&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; &lt;SPAN style="COLOR: blue"&gt;index&lt;/SPAN&gt; T_clu_d &lt;SPAN style="COLOR: blue"&gt;on&lt;/SPAN&gt; T_clu &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;d&lt;SPAN style="COLOR: gray"&gt;)&lt;/SPAN&gt; &lt;SPAN style="COLOR: blue"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: gray"&gt;(&lt;/SPAN&gt;e&lt;SPAN style="COLOR: gray"&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;The covered columns for each index are:&lt;/P&gt;
&lt;P&gt;
&lt;TABLE class=MsoTableGrid style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; BORDER-COLLAPSE: collapse; mso-border-alt: solid windowtext .5pt; mso-padding-alt: 0in 5.4pt 0in 5.4pt; mso-border-insideh: .5pt solid windowtext; mso-border-insidev: .5pt solid windowtext; mso-yfti-tbllook: 480" cellSpacing=0 cellPadding=0 border=1&gt;
&lt;TBODY&gt;
&lt;TR style="mso-yfti-irow: 0; mso-yfti-firstrow: yes"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Index&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Covered Columns&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 1"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_heap_a&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 2"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_heap_bc&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;b, c&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 3"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_heap_d&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;d, e&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 4"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN lang=PT-BR style="mso-ansi-language: PT-BR"&gt;T_clu_a&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;SPAN lang=PT-BR style="mso-ansi-language: PT-BR"&gt;a, b, c, d, e&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 5"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_clu_b&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, b&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 6"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_clu_ac&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, c&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 7; mso-yfti-lastrow: yes"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;T_clu_d&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.5in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=180&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;a, d, e&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P&gt;Note that order is not relevant for covered columns.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;How does this relate to index seeks and bookmark lookups?&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Consider this query (using the above schema):&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; e &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; T_clu &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; b &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 2&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;At first glance, this query looks like a perfect candidate for an index seek using the non-clustered index on column b (T_clu_b) with the seek predicate “b = 2”.&amp;nbsp; However, this index does not cover column e so a seek or scan of this index cannot return the value of column e.&amp;nbsp; The solution is simple.&amp;nbsp; For each row that we fetch from the non-clustered index, we can lookup the value of column e in the clustered index.&amp;nbsp; We call this operation a “bookmark lookup.”&amp;nbsp; A “bookmark” is a pointer to the row in the heap or clustered index.&amp;nbsp; We store the bookmark for each row in the non-clustered index precisely so that we can always navigate from the non-clustered index to the corresponding row in the base table.&lt;/P&gt;
&lt;P&gt;The following figure illustrates a bookmark lookup:&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/652623/original.aspx" border=0&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Showplan Samples&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;In SQL Server 2000, we implement bookmark lookup using a dedicated iterator whether the base table is a clustered index or heap:&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/652624/original.aspx" border=0&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([T_clu]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Index Seek(OBJECT:([T_clu].[T_clu_b]), SEEK:([T_clu].[b]=2) ORDERED FORWARD)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;In SQL Server 2005, we use a regular clustered index seek if the base table is a clustered index or a RID (record id) lookup if the base table is a heap:&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/652625/original.aspx" border=0&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma&gt;&amp;nbsp;&lt;FONT size=1&gt; |--Nested Loops(Inner Join, OUTER REFERENCES:([T_clu].[a]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Index Seek(OBJECT:([T_clu].[T_clu_b]), SEEK:([tempdb].[dbo].[T_clu].[b]=(2)) ORDERED FORWARD)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Clustered Index Seek(OBJECT:([T_clu].[T_clu_a]), SEEK:([T_clu].[a]= [T_clu].[a]) &lt;FONT color=#ff0000&gt;LOOKUP &lt;/FONT&gt;ORDERED FORWARD)&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=1&gt;&lt;/P&gt;
&lt;P&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/652626/original.aspx" border=0&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000]))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--Index Seek(OBJECT:([T_heap].[T_heap_a]), SEEK:([T_heap].[a]=(2)) ORDERED FORWARD)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |--&lt;FONT color=#ff0000&gt;RID Lookup&lt;/FONT&gt;(OBJECT:([T_heap]), SEEK:([Bmk1000]=[Bmk1000]) &lt;FONT color=#ff0000&gt;LOOKUP&lt;/FONT&gt; ORDERED FORWARD)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;You can tell that the clustered index seek is a bookmark lookup by the LOOKUP keyword in text showplan or by the attribute Lookup=“1” in XML showplan.&amp;nbsp; I will explain the behavior of the nested loops join in a future post.&amp;nbsp; The loop join and clustered index seek (or RID lookup) perform the same operation as the bookmark lookup in SQL Server 2000.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Tradeoffs&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Bookmark lookup is not a cheap operation.&amp;nbsp; Assuming (as is commonly the case) that there is no correlation between the non-clustered and clustered index keys, each bookmark lookup performs a random I/O into the clustered index.&amp;nbsp; Random I/Os are very expensive.&amp;nbsp; When comparing various plan alternatives including scans, seeks, and seeks with bookmark lookups, the optimizer must decide whether it is cheaper to perform more sequential I/Os and touch more rows using an index scan or a seek with a less selective predicate that covers all required columns or to perform fewer random I/Os and touch fewer rows using a seek with a more selective predicate and a bookmark lookup.&lt;/P&gt;
&lt;P&gt;In some cases, you can introduce a better plan option by creating a new index or by adding one or more columns to an existing index so as to eliminate a bookmark lookup or change a scan into a seek.&amp;nbsp; In SQL Server 2000, the only way to add columns to an index is to add additional key columns.&amp;nbsp; As I mentioned above, in SQL Server 2005, you can add also columns using the include clause of the create index statement.&amp;nbsp; Included columns are more efficient than key columns; they save disk space and make searching and updating the index more efficient.&lt;/P&gt;
&lt;P&gt;Of course, whenever you create new indexes or add new key or included columns to an existing index, you do consume additional disk space and you make it more expensive to search and update the index.&amp;nbsp; Thus, you must balance the frequency and importance of the queries that benefit from the new index against the queries or updates that are slower.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;To be continued again …&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;In my next post, I’ll go into a bit more detail about when we can perform index seeks and take a look at single vs. multi-column indexes.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=652639" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category></item><item><title>Scans vs. Seeks</title><link>http://blogs.msdn.com/craigfr/archive/2006/06/26/647852.aspx</link><pubDate>Tue, 27 Jun 2006 00:40:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:647852</guid><dc:creator>craigfr</dc:creator><slash:comments>3</slash:comments><comments>http://blogs.msdn.com/craigfr/comments/647852.aspx</comments><wfw:commentRss>http://blogs.msdn.com/craigfr/commentrss.aspx?PostID=647852</wfw:commentRss><description>&lt;P&gt;Scans and seeks are the iterators that SQL Server uses to read data from tables and indexes.&amp;nbsp; These iterators are among the most fundamental ones that we support.&amp;nbsp; They appear in nearly every query plan.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;What is the difference between a scan and a seek?&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;A scan returns the entire table or index.&amp;nbsp; A seek efficiently returns rows from one or more ranges of an index based on a predicate.&amp;nbsp; For example, consider the following query:&lt;/P&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt;select&lt;/SPAN&gt;&lt;SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'; mso-no-proof: yes"&gt; OrderDate &lt;SPAN style="COLOR: blue"&gt;from&lt;/SPAN&gt; Orders &lt;SPAN style="COLOR: blue"&gt;where&lt;/SPAN&gt; OrderKey &lt;SPAN style="COLOR: gray"&gt;=&lt;/SPAN&gt; 2&lt;SPAN style="COLOR: gray"&gt;&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Scan&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;With a scan, we read each row in the orders table, evaluate the predicate “where OrderKey = 2” and, if the predicate is true (i.e., if the row qualifies), return the row.&amp;nbsp; In this case, we refer to the predicate as a “residual” predicate.&amp;nbsp; To maximize performance, whenever possible we evaluate the residual predicate in the scan.&amp;nbsp; However, if the predicate is too expensive, we may evaluate it in a separate filter iterator.&amp;nbsp; The residual predicate appears in text showplan with the WHERE keyword or in XML showplan with the &amp;lt;Predicate&amp;gt; tag.&lt;/P&gt;
&lt;P&gt;Here is the text showplan (slightly edited for brevity) for this query using a scan:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Table Scan(OBJECT:([ORDERS]), WHERE:([ORDERKEY]=(2)))&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The following figure illustrates the scan:&lt;/P&gt;
&lt;P&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/647831/original.aspx" border=0&gt;&lt;/P&gt;
&lt;P&gt;Since a scan touches every row in the table whether or not it qualifies, the cost is proportional to the total number of rows in the table.&amp;nbsp; Thus, a scan is an efficient strategy if the table is small or if most of the rows qualify for the predicate.&amp;nbsp; However, if the table is large and if most of the rows do not qualify, we touch many more pages and rows and perform many more I/Os than is necessary.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Seek&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Going back to&amp;nbsp;the example, if we have an index on OrderKey, a seek may be a better plan.&amp;nbsp; With a seek, we use the index to navigate directly to those rows that satisfy the predicate.&amp;nbsp; In this case, we refer to the predicate as a “seek” predicate.&amp;nbsp; In most cases, we do not need to re-evaluate the seek predicate as a residual predicate; the index ensures that the seek only returns rows that qualify.&amp;nbsp; The seek predicate appears in the text showplan with the SEEK keyword or in XML showplan with the &amp;lt;SeekPredicates&amp;gt; tag.&lt;/P&gt;
&lt;P&gt;Here is the text showplan for the same query using a seek:&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=1&gt;&amp;nbsp; |--Index Seek(OBJECT:([ORDERS].[OKEY_IDX]), SEEK:([ORDERKEY]=(2)) ORDERED FORWARD)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The following figure illustrates the seek:&lt;/P&gt;
&lt;P&gt;&lt;IMG src="http://blogs.msdn.com/photos/craigfr/images/647832/original.aspx" border=0&gt;&lt;/P&gt;
&lt;P&gt;Since a seek only touches rows that qualify and pages that contain these qualifying rows, the cost is proportional to the number of qualifying rows and pages rather than to the total number of rows in the table.&amp;nbsp; Thus, a seek is generally a more efficient strategy if we have a highly selective seek predicate; that is, if we have a seek predicate that eliminates a large fraction of the table.&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;A note about showplan&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;In showplan, we distinguish between scans and seeks as well as between scans on heaps (an object with no index), clustered indexes, and non-clustered indexes.&amp;nbsp; The following table shows all of the valid combinations:&lt;/P&gt;
&lt;P&gt;
&lt;TABLE class=MsoTableGrid style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; BORDER-COLLAPSE: collapse; mso-border-alt: solid windowtext .5pt; mso-padding-alt: 0in 5.4pt 0in 5.4pt; mso-border-insideh: .5pt solid windowtext; mso-border-insidev: .5pt solid windowtext; mso-yfti-tbllook: 480" cellSpacing=0 cellPadding=0 border=1&gt;
&lt;TBODY&gt;
&lt;TR style="mso-yfti-irow: 0; mso-yfti-firstrow: yes"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1.5pt double; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: double 1.5pt; mso-border-top-alt: solid .5pt; mso-border-left-alt: solid .5pt; mso-border-right-alt: double 1.5pt; mso-border-color-alt: windowtext" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: double 1.5pt; mso-border-top-alt: solid .5pt; mso-border-left-alt: double 1.5pt; mso-border-right-alt: solid .5pt; mso-border-color-alt: windowtext" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Scan&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1.5pt double; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-bottom-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Seek&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 1"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1.5pt double; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid .5pt; mso-border-top-alt: double 1.5pt; mso-border-left-alt: solid .5pt; mso-border-right-alt: double 1.5pt; mso-border-color-alt: windowtext" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Heap&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid .5pt; mso-border-top-alt: double 1.5pt; mso-border-left-alt: double 1.5pt; mso-border-right-alt: solid .5pt; mso-border-color-alt: windowtext" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;Table Scan&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: double windowtext 1.5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 2"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1.5pt double; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-right-alt: double windowtext 1.5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Clustered Index&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: double windowtext 1.5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;Clustered Index Scan&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;Clustered Index Seek&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR style="mso-yfti-irow: 3; mso-yfti-lastrow: yes"&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1.5pt double; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: windowtext 1pt solid; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-right-alt: double windowtext 1.5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;&lt;B style="mso-bidi-font-weight: normal"&gt;Non-clustered Index&lt;o:p&gt;&lt;/o:p&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: double windowtext 1.5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;Index Scan&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: #ece9d8; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #ece9d8; WIDTH: 1.75in; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-alt: solid windowtext .5pt; mso-border-top-alt: solid windowtext .5pt; mso-border-left-alt: solid windowtext .5pt" vAlign=top width=210&gt;
&lt;P class=MsoNormal style="MARGIN: 0in 0in 0pt"&gt;Index Seek&lt;o:p&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;To be continued …&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;There is much more to write about scans and seeks.&amp;nbsp; In my next post, I will continue by discussing bookmark lookup and how bookmark lookup relates to scans and seeks.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=647852" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/craigfr/archive/tags/Scans+and+Seeks/default.aspx">Scans and Seeks</category></item></channel></rss>