Double Seek | SQL Server Performance Forums

SQL Server Performance Forum – Threads Archive

Double Seek

<br />Hello,<br /><br />I have a query that produces a queryplan that i find somewhat peculiar.<br /><br />The query is shown below.<br />There is an index on the ‘outorder’ table for "status, comp_id"<br />PK of the table is ‘comp_id, outorder’ (clustered)<br /><br />What I find strange is, which you see if you study the plan, that first it seeks the ‘outorder’ table for all rows with the specified statuses and "comp_id = 0". Then it seeks the table again, this time only for "comp_id = 0 " and then it merges those results.<br />The second seek seems unneccessary to me, since the "comp_id = 0" condition is guaranteed by the first seek!?<br /><br />Anyone have a clue whats going on?<br /><br /><pre id="code"><font face="courier" size="2" id="code"><br />StmtText <br />—————————————————————————————————————————————————————————————————————— <br />select count(*)<br />from outorder oo (NOLOCK)<br />inner join customer cu (NOLOCK)<br />oncu.cust_id = oo.cust_id<br />andcu.comp_id = 0<br />andcu.blockcode_id &gt; 0<br />where oo.comp_id =0<br />and oo.status in (10,20,50)<br /><br />(1 row(s) affected)<br /><br />StmtText <br />———————————————————————————————————————————————————————————————————————————————————————————- <br /> |–Compute Scalar(DEFINE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Expr1002]=Convert([globalagg1004])))<br /> |–Stream Aggregate(DEFINE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[globalagg1004]=SUM([partialagg1003])))<br /> |–Parallelism(Gather Streams)<br /> |–Stream Aggregate(DEFINE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[partialagg1003]=Count(*)))<br /> |–Hash Match(Inner Join, HASH<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[oo].[cust_id])=([cu].[cust_id]))<br /> |–Bitmap(HASH<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[oo].[cust_id]), DEFINE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Bitmap1006]))<br /> | |–Parallelism(Repartition Streams, PARTITION COLUMNS<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[oo].[cust_id]))<br /> | |–Hash Match(Inner Join, HASH<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Bmk1000])=([Bmk1000]), RESIDUAL<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Bmk1000]=[Bmk1000]))<br /> | |–Bitmap(HASH<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Bmk1000]), DEFINE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Bitmap1005]))<br /> | | |–Parallelism(Repartition Streams, PARTITION COLUMNS<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Bmk1000]))<br /> | | |–Index Seek(OBJECT<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[RamosITHTest].[dbo].[outorder].[s2_outorder] AS [oo]), SEEK<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[oo].[comp_id]=0 AND [oo].[status]=10 OR [oo].[comp_id]=0 AND [oo].[status]=20 OR [oo].[comp_id]=0 AND [oo].[status]=50) ORDERED FORWARD)<br /> | |–Parallelism(Repartition Streams, PARTITION COLUMNS<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[Bmk1000]), WHERE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />PROBE([Bitmap1005])=TRUE))<br /> | |–Index Seek(OBJECT<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[RamosITHTest].[dbo].[outorder].[s4_outorder] AS [oo]), SEEK<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[oo].[comp_id]=0) ORDERED FORWARD)<br /> |–Parallelism(Repartition Streams, PARTITION COLUMNS<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[cu].[cust_id]), WHERE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />PROBE([Bitmap1006])=TRUE))<br /> |–Clustered Index Seek(OBJECT<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[RamosITHTest].[dbo].[customer].[PK_customer] AS [cu]), SEEK<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[cu].[comp_id]=0), WHERE<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[cu].[blockcode_id]&gt;0) ORDERED FORWARD)<br /><br />(15 row(s) affected)<br /><br /></font id="code"></pre id="code"><br /><br />Thanks,<br /><br />/Linus<br /><br /><br />–<br /<a target="_blank" href=http://anticAPSLOCK.com>http://anticAPSLOCK.com</a>
the first index seek to the index s2_outorder finds the rows that meet both SARGS (status & comp_id).
next you need the cust_id to do the join.
this could be done with a bookmark lookup for each row,
or with the hash join back to the clustered index.
the second is considered more efficient because you happen to specify the cluster key value.
consider also changing the nonclustered index to status,comp_id,cust_id
<br />That index might very well be a good idea, but I was thinking about this line:<br /><br />|–Index Seek(OBJECT<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[RamosITHTest].[dbo].[outorder].[s4_outorder] AS [oo]), SEEK<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[oo].[comp_id]=0) ORDERED FORWARD)<br /><br />It happens parallell to the index seek on "status, comp_id" and that’s what’s confusing me. Why is that seek needed, when the first seek:<br /><br />|–Index Seek(OBJECT<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[RamosITHTest].[dbo].[outorder].[s2_outorder] AS [oo]), SEEK<img src=’/community/emoticons/emotion-6.gif’ alt=’:(‘ />[oo].[comp_id]=0 AND [oo].[status]=10 OR [oo].[comp_id]=0 AND [oo].[status]=20 OR [oo].[comp_id]=0 AND [oo].[status]=50) ORDERED FORWARD)<br /><br />already specifies "comp_id = 0".<br /><br />/Linus<br /><br /><br />–<br /<a target="_blank" href=http://anticAPSLOCK.com>http://anticAPSLOCK.com</a>
]]>