<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://sqlblog.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>Search results matching tags 'Undocumented' and 'Iterators'</title><link>http://sqlblog.com/search/SearchResults.aspx?o=DateDescending&amp;tag=Undocumented,Iterators&amp;orTags=0</link><description>Search results matching tags 'Undocumented' and 'Iterators'</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>A Tale of Two Index Hints</title><link>http://sqlblog.com/blogs/paul_white/archive/2010/09/22/a-tale-of-two-index-hints.aspx</link><pubDate>Wed, 22 Sep 2010 21:44:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:28917</guid><dc:creator>Paul White</dc:creator><description>&lt;p&gt;If you look up &lt;a href="http://msdn.microsoft.com/en-us/library/ms187373.aspx"&gt;Table Hints&lt;/a&gt; in Books Online, you’ll find the following statement:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;i&gt;If a clustered index exists, INDEX(0) forces a clustered index scan and INDEX(1) forces a clustered index scan or seek.        &lt;br /&gt;If no clustered index exists, INDEX(0) forces a table scan and INDEX(1) is interpreted as an error.&lt;/i&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p align="left"&gt;The interesting thing there is that &lt;i&gt;both&lt;/i&gt; hints can result in a scan.&amp;#160; If that is the case, you might wonder if there is any effective difference between the two.&amp;#160; This blog entry explores that question, and highlights an optimizer quirk that can result in a much less efficient query plan when using INDEX(0).&amp;#160; I’ll also cover some stuff about ordering guarantees.&lt;/p&gt;  &lt;h3&gt;Test Environment&lt;/h3&gt;  &lt;p&gt;As usual, we need to create a test table:&lt;/p&gt;  &lt;div style="border-bottom:silver 1px solid;text-align:left;border-left:silver 1px solid;padding-bottom:4px;line-height:12pt;background-color:#f4f4f4;margin:20px 0px 10px;padding-left:4px;width:97.5%;padding-right:4px;font-family:'Courier New', courier, monospace;direction:ltr;max-height:200px;font-size:8pt;overflow:auto;border-top:silver 1px solid;cursor:text;border-right:silver 1px solid;padding-top:4px;" id="codeSnippetWrapper"&gt;   &lt;pre style="border-bottom-style:none;text-align:left;padding-bottom:0px;line-height:12pt;background-color:#f4f4f4;margin:0em;border-left-style:none;padding-left:0px;width:100%;padding-right:0px;font-family:'Courier New', courier, monospace;direction:ltr;border-top-style:none;color:black;border-right-style:none;font-size:8pt;overflow:visible;padding-top:0px;" id="codeSnippet"&gt;&lt;span style="color:#0000ff;"&gt;USE&lt;/span&gt;     tempdb;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SET&lt;/span&gt;     NOCOUNT &lt;span style="color:#0000ff;"&gt;ON&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;GO&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;IF&lt;/span&gt;      OBJECT_ID(N&lt;span style="color:#006080;"&gt;'Test.Orders'&lt;/span&gt;, N&lt;span style="color:#006080;"&gt;'U'&lt;/span&gt;)&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;IS&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NOT&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NULL&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;DROP&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;TABLE&lt;/span&gt; Test.Orders;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;GO&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;IF&lt;/span&gt;      SCHEMA_ID(N&lt;span style="color:#006080;"&gt;'Test'&lt;/span&gt;)&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;IS&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NOT&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NULL&lt;/span&gt;&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;DROP&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;SCHEMA&lt;/span&gt; Test;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;GO&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;CREATE&lt;/span&gt;  &lt;span style="color:#0000ff;"&gt;SCHEMA&lt;/span&gt; Test&lt;br /&gt;&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;CREATE&lt;/span&gt;  &lt;span style="color:#0000ff;"&gt;TABLE&lt;/span&gt; Orders&lt;br /&gt;                (&lt;br /&gt;                order_id    &lt;span style="color:#0000ff;"&gt;INTEGER&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NOT&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NULL&lt;/span&gt;,&lt;br /&gt;                product_id  &lt;span style="color:#0000ff;"&gt;INTEGER&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NOT&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NULL&lt;/span&gt;,&lt;br /&gt;                quantity    &lt;span style="color:#0000ff;"&gt;INTEGER&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NOT&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NULL&lt;/span&gt;,&lt;br /&gt;                padding     &lt;span style="color:#0000ff;"&gt;CHAR&lt;/span&gt;(980) &lt;span style="color:#0000ff;"&gt;NOT&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;NULL&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;DEFAULT&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;SPACE&lt;/span&gt;(0)),&lt;br /&gt;                &lt;br /&gt;                &lt;span style="color:#0000ff;"&gt;CONSTRAINT&lt;/span&gt;  [PK Test.Orders order_id]&lt;br /&gt;                &lt;span style="color:#0000ff;"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;KEY&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;CLUSTERED&lt;/span&gt; (order_id &lt;span style="color:#0000ff;"&gt;ASC&lt;/span&gt;)&lt;br /&gt;                );&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;GO&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;CREATE&lt;/span&gt;  &lt;span style="color:#0000ff;"&gt;NONCLUSTERED&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;  [IX Test.Orders quantity]&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;ON&lt;/span&gt;      Test.Orders (quantity &lt;span style="color:#0000ff;"&gt;ASC&lt;/span&gt;)&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;INCLUDE&lt;/span&gt; (padding);&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;GO&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;DECLARE&lt;/span&gt; @Counter    &lt;span style="color:#0000ff;"&gt;INTEGER&lt;/span&gt; = 1,&lt;br /&gt;        @&lt;span style="color:#0000ff;"&gt;RowCount&lt;/span&gt;   &lt;span style="color:#0000ff;"&gt;INTEGER&lt;/span&gt; = 64 * 8;&lt;br /&gt;        &lt;br /&gt;&lt;span style="color:#0000ff;"&gt;WHILE&lt;/span&gt;   @Counter &amp;lt;= @&lt;span style="color:#0000ff;"&gt;RowCount&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;BEGIN&lt;/span&gt;&lt;br /&gt;        INSERT  Test.Orders&lt;br /&gt;                (order_id, product_id, quantity)&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;VALUES&lt;/span&gt;  (&lt;br /&gt;                @&lt;span style="color:#0000ff;"&gt;RowCount&lt;/span&gt; - @Counter,&lt;br /&gt;                @&lt;span style="color:#0000ff;"&gt;RowCount&lt;/span&gt; - @Counter,&lt;br /&gt;                @&lt;span style="color:#0000ff;"&gt;RowCount&lt;/span&gt; - @Counter&lt;br /&gt;                );&lt;br /&gt;                &lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;SET&lt;/span&gt;     @Counter += 1;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;END&lt;/span&gt;;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;GO&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;

&lt;p align="left"&gt;The test table has 512 rows, padded so that eight rows fit on each 8KB data page.&amp;#160; The table is clustered on the &lt;i&gt;order_id&lt;/i&gt; column, and there is a nonclustered index on the &lt;i&gt;quantity&lt;/i&gt; column, with the &lt;i&gt;padding&lt;/i&gt; column INCLUDEd to again ensure that eight rows fit on each nonclustered index page.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/paul_white/TableContents_2C94614A.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="Table-Contents" border="0" alt="Table-Contents" src="http://sqlblog.com/blogs/paul_white/TableContents_thumb_16CF9A4F.png" width="270" height="229" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The script also deliberately generates a very high level of logical fragmentation, which we can see with the following query:&lt;/p&gt;

&lt;div style="border-bottom:silver 1px solid;text-align:left;border-left:silver 1px solid;padding-bottom:4px;line-height:12pt;background-color:#f4f4f4;margin:20px 0px 10px;padding-left:4px;width:97.5%;padding-right:4px;font-family:'Courier New', courier, monospace;direction:ltr;max-height:200px;font-size:8pt;overflow:auto;border-top:silver 1px solid;cursor:text;border-right:silver 1px solid;padding-top:4px;" id="codeSnippetWrapper"&gt;
  &lt;pre style="border-bottom-style:none;text-align:left;padding-bottom:0px;line-height:12pt;background-color:#f4f4f4;margin:0em;border-left-style:none;padding-left:0px;width:100%;padding-right:0px;font-family:'Courier New', courier, monospace;direction:ltr;border-top-style:none;color:black;border-right-style:none;font-size:8pt;overflow:visible;padding-top:0px;" id="codeSnippet"&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  SI.name,&lt;br /&gt;        IPS.index_type_desc,&lt;br /&gt;        IPS.avg_fragmentation_in_percent,&lt;br /&gt;        IPS.fragment_count,&lt;br /&gt;        IPS.page_count&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt;    sys.dm_db_index_physical_stats&lt;br /&gt;        (&lt;br /&gt;        DB_ID(),&lt;br /&gt;        OBJECT_ID(N&lt;span style="color:#006080;"&gt;'Test.Orders'&lt;/span&gt;, N&lt;span style="color:#006080;"&gt;'U'&lt;/span&gt;),&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;DEFAULT&lt;/span&gt;,&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;DEFAULT&lt;/span&gt;,&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;DEFAULT&lt;/span&gt;&lt;br /&gt;        ) IPS&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;JOIN&lt;/span&gt;    sys.indexes SI&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;ON&lt;/span&gt;  SI.[object_id] = IPS.[object_id]&lt;br /&gt;        &lt;span style="color:#0000ff;"&gt;AND&lt;/span&gt; SI.index_id = IPS.index_id&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;ORDER&lt;/span&gt;   &lt;span style="color:#0000ff;"&gt;BY&lt;/span&gt;&lt;br /&gt;        IPS.index_id;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;GO&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;

&lt;p&gt;The reason for the fragmentation will become apparent later on.&amp;#160; These are the results I saw on my machine:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/paul_white/IndexPhysicalStats_764AEB4D.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="Index-Physical-Stats" border="0" alt="Index-Physical-Stats" src="http://sqlblog.com/blogs/paul_white/IndexPhysicalStats_thumb_5A3CBD13.png" width="629" height="61" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Background Information on Scans&lt;/h3&gt;

&lt;p&gt;A &lt;i&gt;scan&lt;/i&gt; of a table or index processes every row, whereas a &lt;i&gt;seek&lt;/i&gt; returns one or more rows from one or more ranges of the index.&amp;#160; &lt;/p&gt;

&lt;h4&gt;Scan Strategies&lt;/h4&gt;

&lt;p align="left"&gt;When the SQL Server optimizer produces a plan containing a scan of an index or clustered table, the Storage Engine may be able to choose from two different strategies: scanning the pages in logical order, or the order in which they were allocated.&amp;#160; In the first case, it follows the doubly-linked list at the leaf level of the index.&amp;#160; In the second case, it can use the &lt;a href="http://msdn.microsoft.com/en-us/library/ms187501.aspx"&gt;Index Allocation Map&lt;/a&gt; (IAM) pages to drive the scan.&lt;/p&gt;

&lt;p align="left"&gt;A scan of a heap always uses the IAM pages of course (there is no clustered index to provide a logical page order), but this post is concerned mainly with indexes and clustered tables, so I won’t talk much more about heaps.&lt;/p&gt;

&lt;p align="left"&gt;The advantage of an IAM-driven scan is that pages will tend to be read in physical order on the disk, which generally results in sequential I/O, and promotes effective read-ahead.&amp;#160; How efficient scanning the pages in logical order is depends on the level of logical fragmentation: if fragmentation is high, the scan will tend to result in a high proportion of random I/Os, and read-ahead may be much less effective.&lt;/p&gt;

&lt;p align="left"&gt;The downside to the IAM-driven scan is that it is not guaranteed to return results in logical key order (in fact it usually won’t), and there is an extra start-up cost in accessing the IAM pages, and planning the scan.&lt;/p&gt;

&lt;h4&gt;IAM-driven scans&lt;/h4&gt;

&lt;p&gt;The Storage Engine may choose an IAM-driven scan if:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The index contains 64 or more pages (giving the method a chance to recover the start-up cost); &lt;/li&gt;

  &lt;li&gt;The optimizer indicates that an ordered scan is not required for correct results; and &lt;/li&gt;

  &lt;li&gt;Either the data cannot change during the scan, &lt;b&gt;or&lt;/b&gt; the query indicates that incorrect results are acceptable. &lt;/li&gt;
&lt;/ol&gt;

&lt;p align="left"&gt;Hopefully the first point makes sense on it’s own.&amp;#160; The second point refers to a flag that the optimizer can set on a scanning iterator in a query plan.&amp;#160; This flag shows up as an “Ordered:True” or “Ordered:False” property of the iterator.&amp;#160; When set to true, it indicates that the Storage Engine &lt;i&gt;must&lt;/i&gt; perform a scan in logical key order for the plan to produce correct results.&amp;#160; When set to false, it means that the order does not matter.&amp;#160; In the latter case, the Storage Engine has the &lt;i&gt;choice&lt;/i&gt; of performing either type of scan.&amp;#160; The query plan does not show which type of scan was chosen.&lt;/p&gt;

&lt;p align="left"&gt;The third point comes in two parts, but both relate to the fact that an IAM-driven scan does not take the locks necessary to prevent other concurrent operations from changing the data in the table, or moving the pages around (perhaps as the result of a page split).&lt;/p&gt;

&lt;p align="left"&gt;The simplest way to satisfy the first part (to ensure that the data cannot be changed or moved) is to take a shared table lock.&amp;#160; The second part (acceptable incorrect results) refers to transaction isolation level.&amp;#160; If the scan runs at an effective isolation level of READ UNCOMMITTED (either explicitly or through the use of a NOLOCK hint) we are effectively saying that we don’t care about reading consistent data.&amp;#160; More than that, we imply that we are happy to read the same data twice, or not at all.&lt;/p&gt;

&lt;h4&gt;The Reason for the Fragmentation&lt;/h4&gt;

&lt;p align="left"&gt;Our test table has been created with a high level of fragmentation.&amp;#160; This allows us to see whether the Storage Engine used an IAM-driven scan or not.&amp;#160; If the results come back in logical key order, we can be sure (in these specific examples) that the scan was performed by following the doubly-linked list at the leaf level of the index.&amp;#160; If the results come back in allocation order, they will look very different, and we can infer that an IAM-driven scan was performed.&lt;/p&gt;

&lt;hr /&gt;

&lt;p align="left"&gt;&lt;b&gt;Important&lt;/b&gt;: Please don’t take any of that to mean that you can rely on query results to come back in a particular order in more general cases.&amp;#160; The very simple tests shown here are carefully crafted to encourage the ordering behaviour I have just described, but there are &lt;i&gt;no guarantees&lt;/i&gt;.&amp;#160; Never rely on plan details to provide ordering – always use an ORDER BY clause if you require ordered results.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3&gt;Scanning Examples&lt;/h3&gt;

&lt;p&gt;Let’s look first at two simple queries that return all columns and all rows from our test table:&lt;/p&gt;

&lt;div style="border-bottom:silver 1px solid;text-align:left;border-left:silver 1px solid;padding-bottom:4px;line-height:12pt;background-color:#f4f4f4;margin:20px 0px 10px;padding-left:4px;width:97.5%;padding-right:4px;font-family:'Courier New', courier, monospace;direction:ltr;max-height:200px;font-size:8pt;overflow:auto;border-top:silver 1px solid;cursor:text;border-right:silver 1px solid;padding-top:4px;" id="codeSnippetWrapper"&gt;
  &lt;pre style="border-bottom-style:none;text-align:left;padding-bottom:0px;line-height:12pt;background-color:#f4f4f4;margin:0em;border-left-style:none;padding-left:0px;width:100%;padding-right:0px;font-family:'Courier New', courier, monospace;direction:ltr;border-top-style:none;color:black;border-right-style:none;font-size:8pt;overflow:visible;padding-top:0px;" id="codeSnippet"&gt;&lt;span style="color:#008000;"&gt;-- Query 1&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.*&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt;    Test.Orders T;&lt;br /&gt; &lt;br /&gt;&lt;span style="color:#008000;"&gt;-- Query 2&lt;/span&gt;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.*&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt;    Test.Orders T &lt;br /&gt;&lt;span style="color:#0000ff;"&gt;ORDER&lt;/span&gt;   &lt;span style="color:#0000ff;"&gt;BY&lt;/span&gt; &lt;br /&gt;        T.order_id &lt;span style="color:#0000ff;"&gt;ASC&lt;/span&gt;;&lt;br /&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;

&lt;p align="left"&gt;Both queries produce very similar execution plans, but the first specifies “Ordered:False” while the second specifies “Ordered:True”.&amp;#160; The second example avoids an expensive explicit Sort iterator by requiring the Storage Engine to return rows from the scan in logical key order (the clustered index is on &lt;i&gt;order_id&lt;/i&gt;, remember).&lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/paul_white/Example1_44E1985C.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="Example-1" border="0" alt="Example-1" src="http://sqlblog.com/blogs/paul_white/Example1_thumb_41659823.png" width="660" height="418" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p align="left"&gt;Both queries return records ordered by the clustering key; Query 1 because the Storage Engine cannot perform an IAM-driven scan (we haven’t met all the conditions); and Query 2 because an ordered scan was requested by the optimizer.&lt;/p&gt;

&lt;p align="left"&gt;Query 1 is &lt;i&gt;not&lt;/i&gt; guaranteed to return records in that order, but Query 2 (almost) is.&amp;#160; In case you are wondering what might cause Query 1 to return records in a different order, consider the &lt;a href="http://blogs.msdn.com/b/conor_cunningham_msft/archive/2008/08/27/no-seatbelt-expecting-order-without-order-by.aspx"&gt;effect of a parallel plan&lt;/a&gt;, or Enterprise Edition’s &lt;a href="http://msdn.microsoft.com/en-us/library/ms191475.aspx"&gt;Advanced Scan&lt;/a&gt; feature.&amp;#160; For anyone that follows that second link, I should mention that Advanced Scan is only possible when the Ordered flag is set to False.&lt;/p&gt;

&lt;h4&gt;An IAM-driven Scan&lt;/h4&gt;

&lt;p align="left"&gt;Query 1 above meets two of the conditions for an IAM-driven scan: the index contains at least 64 pages, and the optimizer did not specify Ordered:True.&amp;#160; We can meet the third condition by adding a WITH (TABLOCK) hint:&lt;/p&gt;

&lt;div style="border-bottom:silver 1px solid;text-align:left;border-left:silver 1px solid;padding-bottom:4px;line-height:12pt;background-color:#f4f4f4;margin:20px 0px 10px;padding-left:4px;width:97.5%;padding-right:4px;font-family:'Courier New', courier, monospace;direction:ltr;max-height:200px;font-size:8pt;overflow:auto;border-top:silver 1px solid;cursor:text;border-right:silver 1px solid;padding-top:4px;" id="codeSnippetWrapper"&gt;
  &lt;pre style="border-bottom-style:none;text-align:left;padding-bottom:0px;line-height:12pt;background-color:#f4f4f4;margin:0em;border-left-style:none;padding-left:0px;width:100%;padding-right:0px;font-family:'Courier New', courier, monospace;direction:ltr;border-top-style:none;color:black;border-right-style:none;font-size:8pt;overflow:visible;padding-top:0px;" id="codeSnippet"&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.*&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt;    Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (TABLOCK);&lt;br /&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;

&lt;p&gt;We receive the following result set:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/paul_white/Query1withTABLOCK_1DCE8B2D.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="Query-1-with-TABLOCK" border="0" alt="Query-1-with-TABLOCK" src="http://sqlblog.com/blogs/paul_white/Query1withTABLOCK_thumb_4432FF29.png" width="244" height="237" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p align="left"&gt;Each group of eight records (each 8KB page) comes back ordered by the clustering key, but the overall order of pages is in the order the pages were written.&amp;#160; This is the result of an IAM-driven scan: pages in allocation order, records within the pages in clustered key order.&amp;#160; As you may have guessed, neither of these two observed orderings can be relied on either – I mention them for pure geek interest value.&lt;/p&gt;

&lt;p align="left"&gt;Adding the TABLOCK hint to Query 2 does &lt;i&gt;not&lt;/i&gt; result in an IAM-driven scan, because the optimizer continues to specify Ordered:True on the scan (to avoid the sort that would be necessary to honour the ORDER BY clause).&lt;/p&gt;

&lt;p align="left"&gt;For Query 2, the optimizer does still &lt;i&gt;consider&lt;/i&gt; a plan with an unordered scan followed by a sort, but rejects it as being more expensive than an ordered scan.&amp;#160; For very large tables, the optimizer may calculate that an IAM-driven scan might save more time than would be consumed by the extra sort, and a plan featuring the unordered scan + sort &lt;i&gt;would&lt;/i&gt; be chosen.&amp;#160; This is a heuristic optimization: the optimizer knows nothing about the actual fragmentation level of an index (though it does know how many pages it occupies).&lt;/p&gt;

&lt;h3&gt;Index Hints&lt;/h3&gt;

&lt;p&gt;Let’s now run the Query 1 test again, this time specifying INDEX(0) or INDEX(1) hints, as well as choosing to specify TABLOCK or not:&lt;/p&gt;

&lt;div style="border-bottom:silver 1px solid;text-align:left;border-left:silver 1px solid;padding-bottom:4px;line-height:12pt;background-color:#f4f4f4;margin:20px 0px 10px;padding-left:4px;width:97.5%;padding-right:4px;font-family:'Courier New', courier, monospace;direction:ltr;max-height:200px;font-size:8pt;overflow:auto;border-top:silver 1px solid;cursor:text;border-right:silver 1px solid;padding-top:4px;" id="codeSnippetWrapper"&gt;
  &lt;pre style="border-bottom-style:none;text-align:left;padding-bottom:0px;line-height:12pt;background-color:#f4f4f4;margin:0em;border-left-style:none;padding-left:0px;width:100%;padding-right:0px;font-family:'Courier New', courier, monospace;direction:ltr;border-top-style:none;color:black;border-right-style:none;font-size:8pt;overflow:visible;padding-top:0px;" id="codeSnippet"&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(0));&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(1));&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(0), TABLOCK);&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(1), TABLOCK);&lt;br /&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;

&lt;p align="left"&gt;In all cases, the results are the same: if we specify TABLOCK, we meet the conditions for an IAM-driven scan, and the records comes back in physical order.&amp;#160; If we omit TABLOCK, we happen to get results in clustered-key order – but we know that is just coincidental.&amp;#160; All four query plans look exactly the same as the simple clustered index scans shown earlier, and all feature “Ordered:False”.&lt;/p&gt;

&lt;p&gt;Let’s try the same thing with the ORDER BY clause (Query 2):&lt;/p&gt;

&lt;div style="border-bottom:silver 1px solid;text-align:left;border-left:silver 1px solid;padding-bottom:4px;line-height:12pt;background-color:#f4f4f4;margin:20px 0px 10px;padding-left:4px;width:97.5%;padding-right:4px;font-family:'Courier New', courier, monospace;direction:ltr;max-height:200px;font-size:8pt;overflow:auto;border-top:silver 1px solid;cursor:text;border-right:silver 1px solid;padding-top:4px;" id="codeSnippetWrapper"&gt;
  &lt;pre style="border-bottom-style:none;text-align:left;padding-bottom:0px;line-height:12pt;background-color:#f4f4f4;margin:0em;border-left-style:none;padding-left:0px;width:100%;padding-right:0px;font-family:'Courier New', courier, monospace;direction:ltr;border-top-style:none;color:black;border-right-style:none;font-size:8pt;overflow:visible;padding-top:0px;" id="codeSnippet"&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(0)) &lt;span style="color:#0000ff;"&gt;ORDER&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;BY&lt;/span&gt; T.order_id;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(0), TABLOCK) &lt;span style="color:#0000ff;"&gt;ORDER&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;BY&lt;/span&gt; T.order_id;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(1)) &lt;span style="color:#0000ff;"&gt;ORDER&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;BY&lt;/span&gt; T.order_id;&lt;br /&gt;&lt;span style="color:#0000ff;"&gt;SELECT&lt;/span&gt;  T.* &lt;span style="color:#0000ff;"&gt;FROM&lt;/span&gt; Test.Orders T &lt;span style="color:#0000ff;"&gt;WITH&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;INDEX&lt;/span&gt;(1), TABLOCK) &lt;span style="color:#0000ff;"&gt;ORDER&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;BY&lt;/span&gt; T.order_id;&lt;br /&gt;&lt;/pre&gt;

  &lt;br /&gt;&lt;/div&gt;

&lt;p&gt;All four queries produce the same results, in the same order, and are guaranteed to do so thanks to the ORDER BY clause.&amp;#160; The execution plans are rather different, however:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/paul_white/INDEXhints_7EB08FAE.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="INDEX-hints" border="0" alt="INDEX-hints" src="http://sqlblog.com/blogs/paul_white/INDEXhints_thumb_392E2034.png" width="644" height="83" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both INDEX(1) plans produce the plan shown on the left, with the scan showing “Ordered:True”, and a cost of 0.05. 
  &lt;br /&gt;Both INDEX(0) plans produce the plan shown on the right, with the scan showing “Ordered:False”, and a cost of 0.07.&lt;/p&gt;

&lt;h3&gt;Optimizer Bug, Limitation, or “By Design”?&lt;/h3&gt;

&lt;p align="left"&gt;It seems that INDEX(0) forces “Ordered:False” on the scan.&amp;#160; As a consequence, we get an explicit Sort iterator when the ORDER BY clause is present.&amp;#160; Why should INDEX(0) force an unordered scan?&lt;/p&gt;

&lt;p align="left"&gt;One can imagine a line of reasoning that says we would normally specify INDEX(0) only on a heap table, where an ordered scan is not possible.&amp;#160; The flaw in this argument is that INDEX(1) does not enable us to force a &lt;i&gt;scan&lt;/i&gt; of a clustered table – it will result in a seek if at all possible.&lt;/p&gt;

&lt;h4&gt;INDEX(0) and Ordered:True&lt;/h4&gt;

&lt;p align="left"&gt;Nevertheless, it is &lt;i&gt;possible&lt;/i&gt; to produce “Ordered:True” when INDEX(0) is specified, in an INSERT, UPDATE or DELETE query.&amp;#160; Let’s look at three alternative formulations of a trivial UPDATE query on our test table:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/paul_white/UPDATEExamples_65D96ABE.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="UPDATE-Examples" border="0" alt="UPDATE-Examples" src="http://sqlblog.com/blogs/paul_white/UPDATEExamples_thumb_62C99D7A.png" width="644" height="332" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p align="left"&gt;All three plans feature the “Ordered:True” flag, but the one with INDEX(0) includes a sort on &lt;i&gt;order_id&lt;/i&gt;.&amp;#160; That sort is probably redundant, since the Clustered Index Scan (with “Ordered:True”) ought to be guaranteed to produce rows in that same order.&amp;#160; Not only will that sort slow down your query, it will require a memory grant, and might even spill to &lt;i&gt;tempdb&lt;/i&gt;.&lt;/p&gt;

&lt;h4&gt;A Data Modification Optimization&lt;/h4&gt;

&lt;p align="left"&gt;When performing our UPDATE query, the optimizer takes into account the effect of ordering.&amp;#160; If rows arrive at the update iterator in clustered index order, the pattern of updates will likely result in sequential I/O.&amp;#160; If rows are in a different order, the updates will likely cause random I/O as the index is updated row by row.&lt;/p&gt;

&lt;p align="left"&gt;Often, the optimizer may choose to pre-sort the records on the clustered index key to optimize for sequential I/O.&amp;#160; On other occasions, it may decide that the cost of the sort would exceed the savings made – this is seen when the number of rows to be updated is very small, and the rows can be located most efficiently from a nonclustered index.&lt;/p&gt;

&lt;p align="left"&gt;When the rows are sourced from the clustered index, the optimizer &lt;i&gt;always&lt;/i&gt; adds the “Ordered:True” flag to maximize the chances for sequential I/O, regardless of the estimated number of rows.&amp;#160; This explains how the Clustered Index Scan above manages to acquire the “Ordered:True” flag when INDEX(0) was specified.&amp;#160; It seems that the optimizer misses the fact that by adding the ordering flag, the sort is now redundant.&lt;/p&gt;

&lt;h3&gt;Final Thoughts&lt;/h3&gt;

&lt;p align="left"&gt;Although I have concentrated very much on clustered indexes in this entry, I want to stress that the two scanning strategies apply to both clustered and non-clustered indexes.&amp;#160; I created a nonclustered index on the test table so you can verify for yourself that IAM-driven scans are possible with nonclustered indexes, and the rules are exactly the same.&lt;/p&gt;

&lt;p align="left"&gt;It is also not necessary to specify TABLOCK explicitly to get an IAM-driven scan: if the index in question happens to have row and page locks disabled (using ALTER INDEX (…) SET ALLOW_ROW_LOCKS = OFF, ALLOW_PAGE_LOCKS = OFF) the Storage Engine will recognise that a table lock has to be taken.&amp;#160; Other circumstances like the database being read-only will also meet the underlying requirement that the data cannot change during the scan.&lt;/p&gt;

&lt;p align="left"&gt;Beware of using NOLOCK when the other conditions for an IAM-driven scan are met.&amp;#160; Not only might you read data that has not been committed yet, you can also read &lt;i&gt;committed&lt;/i&gt; data twice, or &lt;i&gt;not at all&lt;/i&gt;.&amp;#160; Your query might also fail completely with the dreaded error: “Could not continue scan with NOLOCK due to data movement”.&lt;/p&gt;

&lt;p align="left"&gt;Finally, watch out for unnecessary sorts in plans that use the INDEX(0) table hint – consider using INDEX(1) instead, or rewriting the query to avoid the hint altogether.&lt;/p&gt;

&lt;p&gt;Paul White 
  &lt;br /&gt;Email: &lt;a href="mailto:SQLkiwi@gmail.com"&gt;SQLkiwi@gmail.com&lt;/a&gt; 

  &lt;br /&gt;Twitter: &lt;a href="http://twitter.com/SQL_Kiwi"&gt;@SQL_Kiwi&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;Further Reading&lt;/h5&gt;

&lt;p&gt;&lt;a href="http://www.sqlmag.com/article/sql-server/quaere-verum-clustered-index-scans-part-i.aspx"&gt;Clustered Index Scans&lt;/a&gt; – Itzik Ben-Gan 

  &lt;br /&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms191475.aspx"&gt;Advanced Scanning&lt;/a&gt; – Books Online 

  &lt;br /&gt;&lt;a href="http://blogs.msdn.com/b/sqlserverstorageengine/archive/2006/11/09/when-can-allocation-order-scans-be-used.aspx"&gt;When IAM Scans Can Be Used&lt;/a&gt; – SQL Server Storage Engine Team 

  &lt;br /&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms187501.aspx"&gt;IAM Pages&lt;/a&gt; – Books Online 

  &lt;br /&gt;&lt;a href="http://www.sqlmag.com/article/news2/beware-the-nolock-hint.aspx"&gt;Beware the NOLOCK hint&lt;/a&gt; – Itzik Ben-Gan&lt;/p&gt;</description></item></channel></rss>