{"id":166,"date":"2009-01-09T00:03:52","date_gmt":"2009-01-08T22:03:52","guid":{"rendered":"https:\/\/www.sqlinthewild.co.za\/?p=166"},"modified":"2015-05-25T14:56:22","modified_gmt":"2015-05-25T12:56:22","slug":"seek-or-scan","status":"publish","type":"post","link":"https:\/\/www.sqlinthewild.co.za\/index.php\/2009\/01\/09\/seek-or-scan\/","title":{"rendered":"Seek or scan?"},"content":{"rendered":"<p>One very common question that I see on the forums is on index seeks and index scans. A query is resulting in a table\/clustered index scan, even though there&#8217;s an index on one or more of the columns been searched on.<\/p>\n<p>One of the more common reasons that this happens is because the index in question is not covering, and SQL has determined that the cost of doing the lookups to fetch the extra columns is higher than the cost of scanning the entire table.<\/p>\n<p>If an index does not cover a query, then bookmark lookups are required to get the additional columns, bookmark lookups are run one row at a time, and are seeks on the clustered index. Hence it&#8217;s clear that bookmark lookups on a large number of rows are exceedingly expensive and that is why SQL will switch to a clustered index\/table scan when lookups are required on a significant percentage of the rows in the table.<\/p>\n<p>So, what constitutes a significant percentage of the rows in the table? 50%? 20%? 10%?<\/p>\n<p><!--more-->The answer&#8217;s often surprising. It&#8217;s usually under 1% of the total rows in the table.<\/p>\n<p>Here&#8217;s some test code to demonstrate that.<\/p>\n<p>Setup code:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE dbo.SeekOrScan(\r\n ID int IDENTITY(1,1) NOT NULL primary key,\r\n SomeNumber int,\r\n padding char(100)\r\n)\r\n\r\nCREATE NONCLUSTERED INDEX idx_SeekOrScan ON dbo.SeekOrScan (SomeNumber)<\/pre>\n<p>Test code:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">insert into SeekOrScan (SomeNumber)\r\nselect top 1000000 0\r\nfrom master..spt_values a cross join master..spt_values b\r\nwhere a.name is null and b.name is null\r\n\r\nupdate SeekOrScan set SomeNumber = ID -- just so we've got sequential numbers for the between\r\nGO\r\n\r\ndbcc freeproccache\r\n\r\nselect * from SeekOrScan\r\nwhere somenumber between 1 and 100000 -- 10% of table\r\n-- Clustered index scan\r\n\r\ndbcc freeproccache\r\n\r\nselect * from SeekOrScan\r\nwhere somenumber between 1 and 50000 -- 5% of table\r\n-- Clustered index scan\r\n\r\ndbcc freeproccache\r\n\r\nselect * from &#x5B;SeekOrScan]\r\nwhere somenumber between 1 and 10000 -- 1%\r\n-- Clustered index scan\r\n\r\ndbcc freeproccache\r\n\r\nselect * from &#x5B;SeekOrScan]\r\nwhere somenumber between 1 and 5000\u00a0 -- 0.5% of table\r\n-- clustered index scan\r\n\r\ndbcc freeproccache\r\n\r\nselect * from &#x5B;SeekOrScan]\r\nwhere somenumber between 1 and 3000\u00a0 -- 0.3% of table\r\n-- nonclustered index seek<\/pre>\n<p>So somewhere around 0.4% of the table, the index seek with key lookup became more expensive than a table scan. The result is specific to this table and will differ for tables of different row size because the point at which a scan of the clustered index becomes cheaper than multiple key lookups depends on the number of pages in the table.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One very common question that I see on the forums is on index seeks and index scans. A query is resulting in a table\/clustered index scan, even though there&#8217;s an index on one or more of the columns been searched&#8230; <a class=\"read-more-button\" href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2009\/01\/09\/seek-or-scan\/\">(Read more)<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[24,15,16],"tags":[],"class_list":["post-166","post","type-post","status-publish","format-standard","hentry","category-indexes","category-sql-server","category-syndication"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7h6n-2G","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/166","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/comments?post=166"}],"version-history":[{"count":2,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/166\/revisions"}],"predecessor-version":[{"id":1534,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/166\/revisions\/1534"}],"wp:attachment":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=166"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/categories?post=166"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/tags?post=166"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}