{"id":213,"date":"2016-05-18T11:30:23","date_gmt":"2016-05-18T10:30:23","guid":{"rendered":"https:\/\/sqldoubleg.live-website.com\/?p=213"},"modified":"2016-05-31T20:30:34","modified_gmt":"2016-05-31T19:30:34","slug":"index-fun-disabling-the-clustered-index","status":"publish","type":"post","link":"https:\/\/www.sqldoubleg.com\/es\/2016\/05\/18\/index-fun-disabling-the-clustered-index\/","title":{"rendered":"Index fun, disabling the Clustered Index"},"content":{"rendered":"<p>Disabling an index is always fun, but disabling the clustered index is even funnier, but probably not for your users, let me show you what can happen.&nbsp;Following a conversation with my junior DBA, I decided to show you what happens when we disable indexes and in particular when we disable the clustered index for a table in SQL Server.<\/p>\n<p>If we go to <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms188388.aspx\" target=\"_blank\">Books Online<\/a>, we can read what exactly happens, so I&#8217;m just going to focus on what I think they are the most important bits.<br \/>\n&nbsp;<br \/>\n<strong>Create the playground<\/strong><\/p>\n<p>Let&#8217;s prepare a small demo to accompany these words. First let&#8217;s check one of our tables&#8217; indexes to see what they look like, I will use the sample database [AdventureWorks2014], you can see how to get it in <a href=\"https:\/\/sqldoubleg.live-website.com\/2015\/09\/13\/day-0-adventureworks-setting-up-adventureworks-sample-database\/\" target=\"_blank\">one of my previous posts<\/a><\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nUSE AdventureWorks2014\r\nGO\r\nSELECT  \t\t\t\r\n\t\tQUOTENAME(DB_NAME()) AS DatabaseName\r\n\t\t, QUOTENAME(OBJECT_SCHEMA_NAME(ix.object_id)) + '.' + QUOTENAME(OBJECT_NAME(ix.object_id)) AS TableName\r\n\t\t, ix.index_id AS index_id \r\n\t\t, ix.name\r\n\t\t, ix.type_desc\r\n\t\t, CASE WHEN ix.is_primary_key = 1\tTHEN 'Yes' WHEN ix.is_primary_key = 0\tTHEN  'No' END AS is_primary_key\r\n\t\t, CASE WHEN ix.is_unique = 1\t\tTHEN 'Yes' WHEN ix.is_unique = 0\t\tTHEN  'No' END AS is_unique\r\n\t\t, CASE WHEN ix.is_disabled = 1\t\tTHEN 'Yes' WHEN ix.is_disabled = 0\t\tTHEN  'No' END AS is_disabled\r\n\t\t, ps.row_count\r\n\tFROM sys.indexes AS ix\r\n\t\tLEFT JOIN sys.dm_db_partition_stats AS ps\r\n\t\t\tON ps.object_id = ix.object_id \r\n\t\t\t\tAND ps.index_id = ix.index_id\r\n\tWHERE ix.OBJECT_ID = OBJECT_ID('HumanResources.Department')\t\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/01_indexes_before_disabling.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/01_indexes_before_disabling.png\" alt=\"01_indexes_before_disabling\" width=\"949\" height=\"90\" class=\"aligncenter size-full wp-image-214\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/01_indexes_before_disabling.png 949w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/01_indexes_before_disabling-300x28.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/01_indexes_before_disabling-150x14.png 150w\" sizes=\"(max-width: 949px) 100vw, 949px\" \/><\/a><br \/>\n&nbsp;<br \/>\nSo we have a couple of indexes on our [HumanResources].[Department] table, the clustered plus a non-clustered index.<\/p>\n<p>The common behavior is basically that when one index is disabled, the query optimizer cannot use it and queries that specify the index in a query hint, would fail. <\/p>\n<p>Before disabling the index, let&#8217;s see how the query plan for this query look like<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nSELECT * FROM [HumanResources].[Department] WHERE Name = 'Human Resources'\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/02_Queryplan_before_disabling.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/02_Queryplan_before_disabling.png\" alt=\"02_Queryplan_before_disabling\" width=\"789\" height=\"251\" class=\"aligncenter size-full wp-image-215\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/02_Queryplan_before_disabling.png 789w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/02_Queryplan_before_disabling-300x95.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/02_Queryplan_before_disabling-150x48.png 150w\" sizes=\"(max-width: 789px) 100vw, 789px\" \/><\/a><\/p>\n<p>We can see the pattern <strong><em>Index Seek + Key Lookup<\/em><\/strong>, so we use both the non-clustered index to locate the row and the clustered to satisfy all those columns not included in the non-clustered.<br \/>\n&nbsp;<br \/>\n<strong>Disabling a non-clustered index<\/strong><\/p>\n<p>Now if we disable the non-clustered index we can observe how things change<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER INDEX AK_Department_Name ON [HumanResources].[Department] DISABLE\r\n\r\nSELECT * FROM [HumanResources].[Department] WHERE Name = 'Human Resources'\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/03_Queryplan_after_disabling.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/03_Queryplan_after_disabling.png\" alt=\"03_Queryplan_after_disabling\" width=\"713\" height=\"180\" class=\"aligncenter size-full wp-image-216\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/03_Queryplan_after_disabling.png 713w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/03_Queryplan_after_disabling-300x76.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/03_Queryplan_after_disabling-150x38.png 150w\" sizes=\"(max-width: 713px) 100vw, 713px\" \/><\/a><\/p>\n<p>Since our non-clustered index is disabled, cannot be used, hence the <strong><em>Clustered Index Scan<\/em><\/strong> in our query plan.<\/p>\n<p>That is not all folks! now let&#8217;s see what happens if we disable our clustered index, but let&#8217;s get the old non-clustered back in place<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER INDEX AK_Department_Name ON [HumanResources].[Department] REBUILD\r\n<\/pre>\n<p>&nbsp;<br \/>\n<strong>Disabling a clustered index<\/strong><\/p>\n<p>Now let&#8217;s go see what if we disable our clustered index<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER INDEX PK_Department_DepartmentID ON [HumanResources].[Department] DISABLE\r\n\r\nSELECT * FROM [HumanResources].[Department] \r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/04_error_after_disabling_clustered.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/04_error_after_disabling_clustered.png\" alt=\"04_error_after_disabling_clustered\" width=\"994\" height=\"96\" class=\"aligncenter size-full wp-image-217\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/04_error_after_disabling_clustered.png 994w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/04_error_after_disabling_clustered-300x29.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/04_error_after_disabling_clustered-150x14.png 150w\" sizes=\"(max-width: 994px) 100vw, 994px\" \/><\/a><\/p>\n<p>BOOM! That wasn&#8217;t nice at all&#8230; <\/p>\n<p>A disabled clustered index would leave our table completely useless. But, hold on a second, maybe if we don&#8217;t need the clustered index, we can get some data back. Let&#8217;s try get some data forcing the optimizer to use a different index.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nSELECT Name FROM [HumanResources].[Department] WITH (INDEX = AK_Department_Name)\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/05_error_query_hint.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/05_error_query_hint.png\" alt=\"05_error_query_hint\" width=\"1104\" height=\"113\" class=\"aligncenter size-full wp-image-219\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/05_error_query_hint.png 1104w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/05_error_query_hint-300x31.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/05_error_query_hint-1024x105.png 1024w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/05_error_query_hint-150x15.png 150w\" sizes=\"(max-width: 1104px) 100vw, 1104px\" \/><\/a><\/p>\n<p>Still no luck, we can read in <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms177456.aspx\" target=\"_blank\">BOL<\/a> that <strong><em>Disabling a clustered index on a table also disables all clustered and nonclustered indexes<\/em><\/strong>. So there you go.<\/p>\n<p>We can run our original query to see how both indexes are indeed disabled.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nSELECT  \t\t\t\r\n\t\tQUOTENAME(DB_NAME()) AS DatabaseName\r\n\t\t, QUOTENAME(OBJECT_SCHEMA_NAME(ix.object_id)) + '.' + QUOTENAME(OBJECT_NAME(ix.object_id)) AS TableName\r\n\t\t, ix.index_id AS index_id \r\n\t\t, ix.name\r\n\t\t, ix.type_desc\r\n\t\t, CASE WHEN ix.is_primary_key = 1\tTHEN 'Yes' WHEN ix.is_primary_key = 0\tTHEN  'No' END AS is_primary_key\r\n\t\t, CASE WHEN ix.is_unique = 1\t\tTHEN 'Yes' WHEN ix.is_unique = 0\t\tTHEN  'No' END AS is_unique\r\n\t\t, CASE WHEN ix.is_disabled = 1\t\tTHEN 'Yes' WHEN ix.is_disabled = 0\t\tTHEN  'No' END AS is_disabled\r\n\t\t, ps.row_count\r\n\tFROM sys.indexes AS ix\r\n\t\tLEFT JOIN sys.dm_db_partition_stats AS ps\r\n\t\t\tON ps.object_id = ix.object_id \r\n\t\t\t\tAND ps.index_id = ix.index_id\r\n\tWHERE ix.OBJECT_ID = OBJECT_ID('HumanResources.Department')\t\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/06_both_index_disabled_query_result.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/06_both_index_disabled_query_result.png\" alt=\"06_both_index_disabled_query_result\" width=\"952\" height=\"104\" class=\"aligncenter size-full wp-image-220\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/06_both_index_disabled_query_result.png 952w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/06_both_index_disabled_query_result-300x33.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/06_both_index_disabled_query_result-150x16.png 150w\" sizes=\"(max-width: 952px) 100vw, 952px\" \/><\/a><\/p>\n<p>Yes, both are disabled but have you also spotted something unusual in the number of rows for the non-clustered index? There are no rows, how come?<\/p>\n<p>That I&#8217;d say is the biggest difference between clustered and non-clustered index, and it&#8217;s when BOL says <strong><em>The index definition of a disabled index remains in the system catalog with no underlying index data<\/em><\/strong> that only applies to non-clustered indexes, for a clustered index the data is there for sure, how else would you REBUILD it if the data is gone?<\/p>\n<p>But you don&#8217;t have to believe me, see it with your eyes, first the clustered<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">DBCC IND('AdventureWorks2014', 'HumanResources.Department', 1)<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/08_DBCC_INC_clustered.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/08_DBCC_INC_clustered.png\" alt=\"08_DBCC_INC_clustered\" width=\"1158\" height=\"106\" class=\"aligncenter size-full wp-image-222\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/08_DBCC_INC_clustered.png 1158w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/08_DBCC_INC_clustered-300x27.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/08_DBCC_INC_clustered-1024x94.png 1024w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/08_DBCC_INC_clustered-150x14.png 150w\" sizes=\"(max-width: 1158px) 100vw, 1158px\" \/><\/a><\/p>\n<p>A couple of pages, fair enough, there are only 12 rows, so fine. And now the non-clustered indexes<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">DBCC IND('AdventureWorks2014', 'HumanResources.Department', 2)<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/09_DBCC_INC_nonclustered.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/09_DBCC_INC_nonclustered.png\" alt=\"09_DBCC_INC_nonclustered\" width=\"893\" height=\"83\" class=\"aligncenter size-full wp-image-223\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/09_DBCC_INC_nonclustered.png 893w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/09_DBCC_INC_nonclustered-300x28.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/09_DBCC_INC_nonclustered-150x14.png 150w\" sizes=\"(max-width: 893px) 100vw, 893px\" \/><\/a><\/p>\n<p>Nothing at all, because disabled non-clustered indexes just keep the metadata, which along with the clustered index data is sufficient to REBUILD the index and bring it back to business.<\/p>\n<p>So this is a nice trick indeed, if you are not sure whether to delete that index or not, DISABLE it first, so the burden goes away, but in case you need to get it back, you only need to REBUILD it. Nice!<\/p>\n<p><strong>BONUS:<\/strong><\/p>\n<p>You shouldn&#8217;t, but if you DISABLE any of your clustered indexes, you might want to check your FOREIGN KEY constraints as they are DISABLED and have to be enabled specifically (same as other non-clustered indexes), it does not happen automatically when you REBUILD the index.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER INDEX ALL ON [HumanResources].[Department] REBUILD\r\nGO\r\nSELECT  \r\n\t\tname\r\n\t\t, QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id))\t\t+ '.' + QUOTENAME(OBJECT_NAME(parent_object_id)) AS [object_name]\r\n\t\t, QUOTENAME(OBJECT_SCHEMA_NAME(referenced_object_id))\t+ '.' + QUOTENAME(OBJECT_NAME(referenced_object_id)) AS [referenced_object_name]\r\n\t\t, is_disabled\r\n\t\t, is_not_trusted\r\n\tFROM sys.foreign_keys WHERE referenced_object_id = OBJECT_ID('HumanResources.Department')\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/10_Foreign_keys_disabled.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/05\/10_Foreign_keys_disabled.png\" alt=\"10_Foreign_keys_disabled\" width=\"908\" height=\"80\" class=\"aligncenter size-full wp-image-224\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/10_Foreign_keys_disabled.png 908w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/10_Foreign_keys_disabled-300x26.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/05\/10_Foreign_keys_disabled-150x13.png 150w\" sizes=\"(max-width: 908px) 100vw, 908px\" \/><\/a><\/p>\n<p>So, we would need to make our constraint enabled and trusted.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER TABLE [HumanResources].[EmployeeDepartmentHistory] WITH CHECK CHECK CONSTRAINT FK_EmployeeDepartmentHistory_Department_DepartmentID \r\n<\/pre>\n<p>And That&#8217;s all folks!<br \/>\n&nbsp;<br \/>\n&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Disabling an index is always fun, but disabling the clustered index is even funnier, but probably not for your users, let me show you what can happen.&nbsp;Following a conversation with my junior&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[35,5,12],"_links":{"self":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/213"}],"collection":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/comments?post=213"}],"version-history":[{"count":0,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/213\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/media?parent=213"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/categories?post=213"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/tags?post=213"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}