<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>BlogmyQuery - BMQ &#187; Sorted Seeks</title>
	<atom:link href="http://blogmyquery.com/index.php/tag/sorted-seeks/feed/" rel="self" type="application/rss+xml" />
	<link>http://blogmyquery.com</link>
	<description></description>
	<lastBuildDate>Fri, 03 Feb 2012 15:17:38 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Sorted Seeks</title>
		<link>http://blogmyquery.com/index.php/2006/04/sorted-seeks/</link>
		<comments>http://blogmyquery.com/index.php/2006/04/sorted-seeks/#comments</comments>
		<pubDate>Wed, 12 Apr 2006 20:28:00 +0000</pubDate>
		<dc:creator>QueryOptTeam</dc:creator>
				<category><![CDATA[Sqlserver]]></category>
		<category><![CDATA[Sorted Seeks]]></category>

		<guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:575241</guid>
		<description><![CDATA[<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The Optimizer model makes several assumptions when making plan choice decisions.<SPAN style="mso-spacerun: yes">&#160; </SPAN>These decisions can be false for particular queries and data sets, and sometimes this can cause plan issues.<SPAN style="mso-spacerun: yes">&#160; </SPAN>One such problem is called “The Sorted Seek Problem” by the Optimizer Team, and it affects plan selection, usually between clustered index or heap scan plans and bookmark lookup plans.<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">When costing a series of bookmark lookup operations (for a heap) or index seek operations (for an index), the costing code will effectively treat these as a series of random I/Os.<SPAN style="mso-spacerun: yes">&#160; </SPAN>These are much more expensive than sequential I/Os because the disk arm needs to be moved and the disk needs to spin around to find the right sector(s).<SPAN style="mso-spacerun: yes">&#160; </SPAN>When the correct page is found, it is loaded into the buffer pool and the engine will cache it for future references.<SPAN style="mso-spacerun: yes">&#160; </SPAN>The costing code also understands, on average, how often a page is likely to be found on subsequent operations in the cache, and it costs these accesses much more cheaply than the original access.<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">When a query needs to perform lookups into an access method, the costing code makes the assumption that rows are <I style="mso-bidi-font-style: normal">uniformly</I> distributed over the pages in the table/index.<SPAN style="mso-spacerun: yes">&#160; </SPAN>Under this assumption, effectively each row that is accessed is expensive because we assume that it needs to be loaded from disk.<SPAN style="mso-spacerun: yes">&#160; </SPAN>(At some point, all pages may be in the buffer pool and no longer require the expensive random I/O operation to load a different record from a previously accessed page).<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">In practice, rows are usually not randomly distributed.<SPAN style="mso-spacerun: yes">&#160; </SPAN>In fact, many common user patterns will insert rows in physically clustered order.<SPAN style="mso-spacerun: yes">&#160; </SPAN>For example, you likely would insert all of the order details for an order at the time that the order was created.<SPAN style="mso-spacerun: yes">&#160; </SPAN>These could end up clustered on a very few data pages.<SPAN style="mso-spacerun: yes">&#160; </SPAN>As such, a plan that does a bookmark or index lookup into this structure may complete more quickly than the costing model has forecast.<SPAN style="mso-spacerun: yes">&#160; </SPAN>As a result, the Query Optimizer may pick a different plan that runs more slowly for the customer’s installation.<SPAN style="mso-spacerun: yes">&#160; </SPAN><o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">To be fair, there are a number of scenarios where the rows <I style="mso-bidi-font-style: normal">are</I> uniformly distributed, so they would be broken under a different assumption.<SPAN style="mso-spacerun: yes">&#160; </SPAN>In the future, we may be able to provide additional features to find and fix such problems for customers.<SPAN style="mso-spacerun: yes">&#160; </SPAN>Today, your best option in such situations is to use query hints to force the index plan if you know that you have data that is physically clustered.<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The following sanitized customer example demonstrates this scenario in more detail.<SPAN style="mso-spacerun: yes">&#160; </SPAN>The table is contains about 80 columns and 5.5 million rows.<SPAN style="mso-spacerun: yes">&#160; </SPAN>In the query, the filter qualifies just over 21,000 rows.<SPAN style="mso-spacerun: yes">&#160; </SPAN><o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">SELECT * FROM CustTable<SPAN style="mso-spacerun: yes">&#160; </SPAN>WHERE col='someconstvalue'<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><SPAN style="mso-spacerun: yes">&#160; </SPAN>&#124;--Parallelism(Gather Streams)<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><SPAN style="mso-spacerun: yes">&#160;&#160;&#160;&#160;&#160;&#160; </SPAN>&#124;--Table Scan(OBJECT:(CustTable), WHERE:(col=’someconstvalue’))<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Table ‘CustTable’. Scan count 3, logical reads 416967, physical reads 22, read-ahead reads 413582, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Completed in 3 minutes 9 seconds<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">SELECT * FROM CustTable with (index=idxoncol) WHERE col='someconstvalue'<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><SPAN style="mso-spacerun: yes">&#160; </SPAN>&#124;--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000], [Expr1005]) WITH UNORDERED PREFETCH)<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><SPAN style="mso-spacerun: yes">&#160;&#160;&#160;&#160;&#160;&#160; </SPAN>&#124;--Index Seek(OBJECT:(idxoncol), SEEK:(col=’someconstvalue’) ORDERED FORWARD)<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><SPAN style="mso-spacerun: yes">&#160;&#160;&#160;&#160;&#160;&#160; </SPAN>&#124;--RID Lookup(OBJECT:(CustTable), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Table 'CustTable'. Scan count 1, logical reads 21167, physical reads 387, read-ahead reads 1354, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Completed in 11 seconds<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The first query is the original user query, and the default plan picked is a parallel scan of the base table, applying the filter as a pushed non-SARGable predicate into the scan.<SPAN style="mso-spacerun: yes">&#160; </SPAN>It takes over 3 minutes to complete. The second plan uses an index hint to force a bookmark lookup plan.<SPAN style="mso-spacerun: yes">&#160; </SPAN>It completes in 11 seconds.<SPAN style="mso-spacerun: yes">&#160; </SPAN>If you assume that a random disk seek takes approximately 10 ms, you can’t do more than 100 in a second.<SPAN style="mso-spacerun: yes">&#160; </SPAN>For this query, 21,000 seeks would probably take over 3 minutes to do if each was a random I/O against a single disk.<SPAN style="mso-spacerun: yes">&#160; </SPAN>Therefore, there are likely fewer than 21,000 random I/Os happening.<SPAN style="mso-spacerun: yes">&#160; </SPAN>We can see this from the statistics I/O output – there are only 387 + 1354 actual page reads in the query.<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The actual data distribution is not uniform.<SPAN style="mso-spacerun: yes">&#160; </SPAN>In fact, it is heavily physically grouped.<SPAN style="mso-spacerun: yes">&#160; </SPAN>I ran a query that counted the physical row position in the heap and then filtered out the rows that qualified in this query.<SPAN style="mso-spacerun: yes">&#160; </SPAN>The rows should be somewhat randomly distributed through the set of rows to match the uniformity assumption.<SPAN style="mso-spacerun: yes">&#160; </SPAN>I copied this output into Excel and charted it:<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p></o:p></SPAN>&#160;</P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>[see attachment]&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><v:shapetype id=_x0000_t75 stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype><o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">(The pre-filter row number is the y-axis, and the post-filter row number is the x-axis).<o:p></o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><o:p>&#160;</o:p></SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">As you can see, the rows are very clustered on the disk.<SPAN style="mso-spacerun: yes">&#160; </SPAN>As such, many of the rows used in the bookmark lookups are found on pages that are already loaded into the buffer pool from previous I/Os.<SPAN style="mso-spacerun: yes">&#160; </SPAN>This significantly speeds up the query.&#160; </SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"></SPAN>&#160;</P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The recomendation for these cases is to use query hints if this is a significant issue for your query.</SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"></SPAN>&#160;</P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Enjoy,</SPAN></P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"></SPAN>&#160;</P>
<P class=MsoNormal style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Conor</SPAN></P><img src="http://blogs.msdn.com/aggbug.aspx?PostID=575241" width="1" height="1">]]></description>
			<content:encoded><![CDATA[<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The Optimizer model makes several assumptions when making plan choice decisions.<span style="mso-spacerun: yes"> </span>These decisions can be false for particular queries and data sets, and sometimes this can cause plan issues.<span style="mso-spacerun: yes"> </span>One such problem is called “The Sorted Seek Problem” by the Optimizer Team, and it affects plan selection, usually between clustered index or heap scan plans and bookmark lookup plans.</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">When costing a series of bookmark lookup operations (for a heap) or index seek operations (for an index), the costing code will effectively treat these as a series of random I/Os.<span style="mso-spacerun: yes"> </span>These are much more expensive than sequential I/Os because the disk arm needs to be moved and the disk needs to spin around to find the right sector(s).<span style="mso-spacerun: yes"> </span>When the correct page is found, it is loaded into the buffer pool and the engine will cache it for future references.<span style="mso-spacerun: yes"> </span>The costing code also understands, on average, how often a page is likely to be found on subsequent operations in the cache, and it costs these accesses much more cheaply than the original access.</span></p>

<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"><span id="more-209"></span>
</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">When a query needs to perform lookups into an access method, the costing code makes the assumption that rows are <em style="mso-bidi-font-style: normal">uniformly</em> distributed over the pages in the table/index.<span style="mso-spacerun: yes"> </span>Under this assumption, effectively each row that is accessed is expensive because we assume that it needs to be loaded from disk.<span style="mso-spacerun: yes"> </span>(At some point, all pages may be in the buffer pool and no longer require the expensive random I/O operation to load a different record from a previously accessed page).</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">In practice, rows are usually not randomly distributed.<span style="mso-spacerun: yes"> </span>In fact, many common user patterns will insert rows in physically clustered order.<span style="mso-spacerun: yes"> </span>For example, you likely would insert all of the order details for an order at the time that the order was created.<span style="mso-spacerun: yes"> </span>These could end up clustered on a very few data pages.<span style="mso-spacerun: yes"> </span>As such, a plan that does a bookmark or index lookup into this structure may complete more quickly than the costing model has forecast.<span style="mso-spacerun: yes"> </span>As a result, the Query Optimizer may pick a different plan that runs more slowly for the customer’s installation.<span style="mso-spacerun: yes"> </span></span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">To be fair, there are a number of scenarios where the rows <em style="mso-bidi-font-style: normal">are</em> uniformly distributed, so they would be broken under a different assumption.<span style="mso-spacerun: yes"> </span>In the future, we may be able to provide additional features to find and fix such problems for customers.<span style="mso-spacerun: yes"> </span>Today, your best option in such situations is to use query hints to force the index plan if you know that you have data that is physically clustered.</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The following sanitized customer example demonstrates this scenario in more detail.<span style="mso-spacerun: yes"> </span>The table is contains about 80 columns and 5.5 million rows.<span style="mso-spacerun: yes"> </span>In the query, the filter qualifies just over 21,000 rows.<span style="mso-spacerun: yes"> </span></span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">SELECT * FROM CustTable<span style="mso-spacerun: yes"> </span>WHERE col='someconstvalue'</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><span style="mso-spacerun: yes"> </span>|--Parallelism(Gather Streams)</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><span style="mso-spacerun: yes"> </span>|--Table Scan(OBJECT:(CustTable), WHERE:(col=’someconstvalue’))</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Table ‘CustTable’. Scan count 3, logical reads 416967, physical reads 22, read-ahead reads 413582, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Completed in 3 minutes 9 seconds</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">SELECT * FROM CustTable with (index=idxoncol) WHERE col='someconstvalue'</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><span style="mso-spacerun: yes"> </span>|--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000], [Expr1005]) WITH UNORDERED PREFETCH)</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><span style="mso-spacerun: yes"> </span>|--Index Seek(OBJECT:(idxoncol), SEEK:(col=’someconstvalue’) ORDERED FORWARD)</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"><span style="mso-spacerun: yes"> </span>|--RID Lookup(OBJECT:(CustTable), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Table 'CustTable'. Scan count 1, logical reads 21167, physical reads 387, read-ahead reads 1354, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: Arial">Completed in 11 seconds</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The first query is the original user query, and the default plan picked is a parallel scan of the base table, applying the filter as a pushed non-SARGable predicate into the scan.<span style="mso-spacerun: yes"> </span>It takes over 3 minutes to complete. The second plan uses an index hint to force a bookmark lookup plan.<span style="mso-spacerun: yes"> </span>It completes in 11 seconds.<span style="mso-spacerun: yes"> </span>If you assume that a random disk seek takes approximately 10 ms, you can’t do more than 100 in a second.<span style="mso-spacerun: yes"> </span>For this query, 21,000 seeks would probably take over 3 minutes to do if each was a random I/O against a single disk.<span style="mso-spacerun: yes"> </span>Therefore, there are likely fewer than 21,000 random I/Os happening.<span style="mso-spacerun: yes"> </span>We can see this from the statistics I/O output – there are only 387 + 1354 actual page reads in the query.</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The actual data distribution is not uniform.<span style="mso-spacerun: yes"> </span>In fact, it is heavily physically grouped.<span style="mso-spacerun: yes"> </span>I ran a query that counted the physical row position in the heap and then filtered out the rows that qualified in this query.<span style="mso-spacerun: yes"> </span>The rows should be somewhat randomly distributed through the set of rows to match the uniformity assumption.<span style="mso-spacerun: yes"> </span>I copied this output into Excel and charted it:</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">[see attachment] </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">(The pre-filter row number is the y-axis, and the post-filter row number is the x-axis).</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">As you can see, the rows are very clustered on the disk.<span style="mso-spacerun: yes"> </span>As such, many of the rows used in the bookmark lookups are found on pages that are already loaded into the buffer pool from previous I/Os.<span style="mso-spacerun: yes"> </span>This significantly speeds up the query. </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The recomendation for these cases is to use query hints if this is a significant issue for your query.</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Enjoy,</span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> </span></p>
<p class="MsoNormal" style="MARGIN: 0in 0in 0pt; TEXT-ALIGN: justify"><span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Conor</span></p>
Source : http://blogs.msdn.com/queryoptteam/default.aspx]]></content:encoded>
			<wfw:commentRss>http://blogmyquery.com/index.php/2006/04/sorted-seeks/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

