<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://www2.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>Kalen Delaney : query tuning</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/query+tuning/default.aspx</link><description>Tags: query tuning</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>Did You Know: My next web seminar is this Wednesday!</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2010/06/27/my-next-web-seminar-is-this-wednesday.aspx</link><pubDate>Mon, 28 Jun 2010 03:40:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:26487</guid><dc:creator>Kalen Delaney</dc:creator><slash:comments>2</slash:comments><comments>http://www2.sqlblog.com/blogs/kalen_delaney/comments/26487.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/kalen_delaney/commentrss.aspx?PostID=26487</wfw:commentRss><description>I know, I haven't made a geeky post in a while... but I promise that I'll do one after this seminar. I'll take some of the best questions that I get during the chat and write them up here. Query Plans Workshop (June 30, 2010) Learn about basic elements,...(&lt;a href="http://www2.sqlblog.com/blogs/kalen_delaney/archive/2010/06/27/my-next-web-seminar-is-this-wednesday.aspx"&gt;read more&lt;/a&gt;)&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=26487" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/training/default.aspx">training</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/showplan/default.aspx">showplan</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/seminars/default.aspx">seminars</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/query+tuning/default.aspx">query tuning</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/query+plans/default.aspx">query plans</category></item><item><title>Geek City QUIZ: What happens when you change a column in an index from a key column to an included column?</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2008/11/24/change-a-column-in-an-index-from-a-key-column-to-an-included-column.aspx</link><pubDate>Mon, 24 Nov 2008 07:15:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:10065</guid><dc:creator>Kalen Delaney</dc:creator><slash:comments>5</slash:comments><comments>http://www2.sqlblog.com/blogs/kalen_delaney/comments/10065.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/kalen_delaney/commentrss.aspx?PostID=10065</wfw:commentRss><description>&lt;P&gt;This quiz is not really a generic question about changing an index key column to be an included column; it's about a behavior noticed by a reader in one particular query.&amp;nbsp; Dejan Nakarada-Kordic from New Zealand sent me a very interesting puzzle. He had a reproducible query for which he thought an existing nonclustered index should be used, and it only ended up being used if the column was defined as an INCLUDED column instead of a key column. Here is the script Dejan sent me:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;-- First, create the table&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;IF&amp;nbsp; EXISTS (SELECT * FROM sys.tables &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; WHERE schema_id = 1 and name = 'Table1') &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE dbo.Table1;&lt;BR&gt;GO&lt;BR&gt;CREATE TABLE dbo.Table1(&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Transaction_Serial_No [bigint IDENTITY(1,1) NOT NULL,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Channel] char(4) NOT NULL,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Retry_Counter int NOT NULL,&lt;BR&gt;CONSTRAINT PK_Table1 PRIMARY KEY CLUSTERED &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (Transaction_Serial_No)); &lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;GO &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;--populate the table with 10000 rows&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;SET NOCOUNT ON;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;declare @counter int;&lt;BR&gt;set @counter = 0;&lt;BR&gt;while (@counter &amp;lt; 10000)&lt;BR&gt;begin&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; set @counter = @counter + 1;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; insert into&amp;nbsp; dbo.Table1(channel, retry_counter)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; values('ch1', 0);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; insert into&amp;nbsp; dbo.Table1(channel,&amp;nbsp; retry_counter)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; values('ch2', 0);&lt;BR&gt;end;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;-- Now build a nonclustered index on &lt;BR&gt;IF&amp;nbsp; EXISTS (SELECT * FROM sys.indexes &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; WHERE object_id = OBJECT_ID('dbo.Table1') AND name = 'IX_Channel')&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP INDEX IX_Channel ON dbo.Table1;&lt;BR&gt;GO&lt;BR&gt;CREATE NONCLUSTERED INDEX IX_Channel ON dbo.Table1 (Channel, Retry_Counter);&lt;BR&gt;GO&lt;BR&gt;&lt;BR&gt;-- Examine the query plan for this query:&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=1&gt;SELECT TOP(20) Transaction_Serial_No&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM dbo.Table1&amp;nbsp; &lt;BR&gt;WHERE Channel = 'ch2'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AND Retry_Counter &amp;lt;= 10&amp;nbsp; &lt;BR&gt;ORDER BY Transaction_Serial_No;&lt;/FONT&gt;&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;The plan should show you a Clustered Index Scan being performed, even though the nonclustered index appears to be a covering index. &lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_2.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=67 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_thumb.png" width=261 border=0&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;The query contains all 3 columns used in the query. The columns &lt;EM&gt;Channel&lt;/EM&gt; and &lt;EM&gt;Retry_Counter&lt;/EM&gt; are defined index keys, and because the table has a clustered index, the clustered key &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; is also part of the index. &lt;/P&gt;
&lt;P&gt;If you rebuild the index to use &lt;EM&gt;Retry_Counter&lt;/EM&gt; as an INCLUDED column, the plan changes.&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;CREATE NONCLUSTERED INDEX IX_Channel ON dbo.Table1 (Channel)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; INCLUDE ( Retry_Counter)&amp;nbsp; WITH&amp;nbsp;&amp;nbsp; (DROP_EXISTING = ON);&lt;/FONT&gt; 
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;-- Examine the plan again:&lt;/FONT&gt; 
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;SELECT TOP(20) Transaction_Serial_No&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM dbo.Table1&amp;nbsp; &lt;BR&gt;WHERE Channel = 'ch2'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AND Retry_Counter &amp;lt;= 10&amp;nbsp; &lt;BR&gt;ORDER BY Transaction_Serial_No;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The plan now shows a nonclustered index seek:&lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_4.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=48 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_thumb_1.png" width=244 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;WHY should changing the column from a key column to an INCLUDED column change the plan?&lt;/P&gt;
&lt;P&gt;You can try to figure out the answer for yourself, or read on. &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;-------------------------------------------------------------------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;It turns out that there are two extra factors to consider. &lt;/P&gt;
&lt;P&gt;1) Although covering indexes are a great thing, and the optimizer will choose them over other possible indexes most of the time, the cost of sorting can sometimes outweigh the benefit of a covering index.&lt;/P&gt;
&lt;P&gt;2) When a column is an INCLUDED column in an index, it comes after all the key columns and after the clustered index key that is always part of a nonclustered index on a table that has a clustered index.&lt;/P&gt;
&lt;P&gt;So, the original index on (Channel, Retry_Counter) is the same as an index on (Channel, Retry_Counter, Transaction_Serial_No), but moving &lt;EM&gt;Retry_Counter&lt;/EM&gt; to be an INCLUDED column changes the index to be on the columns (Channel,&amp;nbsp; Transaction_Serial_No), with Retry_Counter in last place, and unsorted.&lt;/P&gt;
&lt;P&gt;The query wants the data sorted on &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt;, and scanning the clustered index gives us the data in &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; order.&amp;nbsp; The first&amp;nbsp; nonclustered index is sorted first by &lt;EM&gt;Channel&lt;/EM&gt;, then by &lt;EM&gt;Retry_Counter&lt;/EM&gt;, then by &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; so that index is not useful for the sort.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;The second nonclustered index is sorted first by &lt;EM&gt;Channel&lt;/EM&gt;, then by &lt;EM&gt;Transaction_Serial_No. &lt;/EM&gt;BUT the query has limited the data to only data with the &lt;EM&gt;Channel&lt;/EM&gt; value equal to 'ch2', so since the first column values we're retrieving are all the same, returning data in the nonclustered index order will return the data in &lt;EM&gt;Transaction_Serial_No&lt;/EM&gt; order and no sorting needs to be done. &lt;/P&gt;
&lt;P&gt;You might notice in this data set that all the values for &lt;EM&gt;Retry_Counter&lt;/EM&gt; are also identical, so you might then think we should be able to use the first nonclustered index to avoid having to sort the data, but the optimizer cannot be sure that the &lt;EM&gt;Retry_Counter&lt;/EM&gt; values are all identical. The statistics might indicate that all the values are the same, but the optimizer can't be sure the statistics are 100% up to date. &lt;/P&gt;
&lt;P&gt;Here are some things to try:&lt;/P&gt;
&lt;P&gt;1) If you remove the ORDER BY, you will see that the first index, with no INCLUDED column, is used.&lt;/P&gt;
&lt;P&gt;2) If you change the second condition to searching for a constant (AND Retry_Counter = 0), the first index can be used. If both of the first two index columns are constants in the retrieved data, it will be sorted by the third column (&lt;EM&gt;Transaction_Serial_No&lt;/EM&gt;).&lt;/P&gt;
&lt;P&gt;3) If you force the query to use the nonclustered index when &lt;EM&gt;Retry_Counter&lt;/EM&gt; is a key, you'll see that SQL Server has to do a sort:&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=1&gt;DROP INDEX IX_Channel ON dbo.Table1;&lt;BR&gt;GO&lt;BR&gt;CREATE NONCLUSTERED INDEX IX_Channel ON dbo.Table1 (Channel, Retry_Counter);&lt;BR&gt;GO&lt;BR&gt;SELECT TOP(20) Transaction_Serial_No&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM dbo.Table1 WITH (index = ix_channel)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE Channel = 'ch2'&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AND Retry_Counter &amp;lt;=&amp;nbsp; 10&lt;BR&gt;ORDER BY Transaction_Serial_No;&lt;BR&gt;GO&lt;/FONT&gt; 
&lt;P&gt;&lt;BR&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_6.png"&gt;&lt;IMG style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height=47 alt=image src="http://sqlblog.com/blogs/kalen_delaney/WindowsLiveWriter/GeekCityQUIZWhathappenswhenyouchangeacol_13A9B/image_thumb_2.png" width=244 border=0&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;Notice that the SORT operation is estimated to be 95% of the total query cost, so SQL Server would definitely like to avoid that if possible.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;So the issue wasn't really because of INCLUDED columns, it was because of the order the columns appeared in the index, and the fact that SQL Server wants to try to avoid sorting whenever possible.&lt;/P&gt;
&lt;P&gt;Have fun!&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff00ff size=4&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=10065" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/sort/default.aspx">sort</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/included+columns/default.aspx">included columns</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/indexes/default.aspx">indexes</category><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/query+tuning/default.aspx">query tuning</category></item><item><title>Did You Know: Can Query Tuning Become Unnecessary?</title><link>http://www2.sqlblog.com/blogs/kalen_delaney/archive/2008/09/07/can-query-tuning-become-unnecessary.aspx</link><pubDate>Mon, 08 Sep 2008 01:49:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:8771</guid><dc:creator>Kalen Delaney</dc:creator><slash:comments>25</slash:comments><comments>http://www2.sqlblog.com/blogs/kalen_delaney/comments/8771.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/kalen_delaney/commentrss.aspx?PostID=8771</wfw:commentRss><description>&lt;P&gt;I wrote a commentary for the SQL Server Magazine e-newsletter last week, and was expecting a to get a bit more feedback than I did.So I'm hoping my&amp;nbsp;blog readers can help out. &amp;nbsp;I was commenting on a message that one of my business partners had sent out wondering about the changes that new higher speed storage technology might bring.&lt;/P&gt;
&lt;P&gt;Please read the whole commentary &lt;A href="http://www.sqlmag.com/Article/ArticleID/100181/sql_server_100181.html" target=_blank&gt;here&lt;/A&gt; (It's not that long.)&lt;/P&gt;
&lt;P&gt;Basically, the comment is that new storage technology is appearing that could have I/O's per second (IOPS) rates thousand of times faster than they are now. &lt;/P&gt;
&lt;P&gt;Most of the reasons for building good indexes and tuning our queries is to reduce the number of reads that SQL Server would have to perform. But if the reads are so fast that even with tables scans our queries are always 'fast enough', would you spend time on query and index tuning?&lt;/P&gt;
&lt;P&gt;At this point, my question is NOT whether you think there will be available and affordable storage systems that can perform as suggested. My question is "IF there were such systems, would you still spend time tuning your queries and evaluating the best indexes?"&amp;nbsp; Also, if query tuning does become unnecessary, what would you do instead to maintain your database system's performance? Or would you be out of a job?&amp;nbsp; &lt;/P&gt;
&lt;P&gt;I'd really like to hear what you think!&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;Thanks&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff00ff size=4&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=8771" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/kalen_delaney/archive/tags/query+tuning/default.aspx">query tuning</category></item></channel></rss>