{"id":1161,"date":"2015-03-01T22:46:35","date_gmt":"2015-03-01T21:46:35","guid":{"rendered":"http:\/\/www.sqlserver.fr\/blog\/?p=1161"},"modified":"2026-05-02T14:30:45","modified_gmt":"2026-05-02T12:30:45","slug":"plan-de-maintenance-et-tables-sans-index-clustered","status":"publish","type":"post","link":"https:\/\/www.sqlserver.fr\/blog\/plan-de-maintenance-et-tables-sans-index-clustered\/","title":{"rendered":"Plan de maintenance et tables sans index clustered"},"content":{"rendered":"<p>M\u00eame si certains oublient de les mettre en place (et s&rsquo;\u00e9tonnent ensuite de voir les performances de leur application se d\u00e9grader au fil du temps &#8230;), des plans de maintenance sont g\u00e9n\u00e9ralement\u00a0impl\u00e9ment\u00e9s\u00a0sur les serveurs SQL Server, et int\u00e8grent notamment une d\u00e9fragmentation des index. Mais dans certains cas, ce type de t\u00e2ches n&rsquo;est pas suffisant &#8230;<!--more--><\/p>\n<p>Dans les exemples de tables que nous prendrons ici, nous consid\u00e9rerons que l&rsquo;on a un certain nombre (10 millions par exemple) d&rsquo;enregistrements dans nos tables, et que nous en supprimons les\u00a0trois quarts. Ensuite, nous progresserons dans notre plan de maintenance pour en observer l&rsquo;efficacit\u00e9.\u00a0L&rsquo;\u00e9tude sera faite sur\u00a02 tables : une avec un index clustered, et l&rsquo;autre sans.<\/p>\n<p>Voici le script de cr\u00e9ation des tables :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE TABLE AvecClustered (Id int IDENTITY PRIMARY KEY, Valeur varchar(100))\r\nCREATE TABLE SansClustered (Id int IDENTITY PRIMARY KEY NONCLUSTERED, Valeur varchar(100))<\/pre>\n<p>Et voici le script de remplissage :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">SET NOCOUNT ON\r\nGO\r\nINSERT INTO AvecClustered (Valeur) SELECT NEWID()\r\nINSERT INTO SansClustered (Valeur) SELECT NEWID()\r\nGO 10000000<\/pre>\n<p>On peut d&rsquo;ores et d\u00e9j\u00e0 observer la taille occup\u00e9e sur disque par ces deux tables :<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/TablesPleines.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1173\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/TablesPleines-620x79.png\" alt=\"TablesPleines\" width=\"620\" height=\"79\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/TablesPleines-620x79.png 620w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/TablesPleines-300x38.png 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/TablesPleines-624x79.png 624w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/TablesPleines.png 730w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p>Ensuite, on efface 3 enregistrements sur 4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">DELETE FROM AvecClustered where Id%4&lt;&gt;0\r\nDELETE FROM SansClustered where Id%4&lt;&gt;0<\/pre>\n<p>En regardant l&rsquo;espace occup\u00e9, on s&rsquo;aper\u00e7oit qu&rsquo;il n&rsquo;a pas chang\u00e9 alors que le nombre d&rsquo;enregistrements a \u00e9t\u00e9 divis\u00e9 par 4 !<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Volume_Gruyere.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1177\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Volume_Gruyere-620x78.png\" alt=\"Volume_Gruyere\" width=\"620\" height=\"78\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Volume_Gruyere-620x78.png 620w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Volume_Gruyere-300x38.png 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Volume_Gruyere-624x79.png 624w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Volume_Gruyere.png 729w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p>Utilisons ensuite l&rsquo;assistant de plan de maintenance pour g\u00e9n\u00e9rer une t\u00e2che basique de reconstruction d&rsquo;index.<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/PlanMaintenance.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1179\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/PlanMaintenance-620x215.png\" alt=\"PlanMaintenance\" width=\"620\" height=\"215\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/PlanMaintenance-620x215.png 620w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/PlanMaintenance-300x104.png 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/PlanMaintenance-624x216.png 624w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/PlanMaintenance.png 887w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p>Et apr\u00e8s ex\u00e9cution de ce plan, voici les volumes occup\u00e9s par les deux tables :<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Moitie.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1180\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Moitie-620x77.png\" alt=\"Moitie\" width=\"620\" height=\"77\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Moitie-620x77.png 620w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Moitie-300x37.png 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Moitie-624x78.png 624w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/02\/Moitie.png 730w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p>On voit donc que le plan de maintenance, destin\u00e9 \u00e0 reconstruire tous les index, a bien consid\u00e9rablement r\u00e9duit le volume occup\u00e9 par la table AvecClustered. Mais il n&rsquo;a pas touch\u00e9 \u00e0 la table SansClustered, qui est un segment m\u00e9moire sans aucun index clustered.<\/p>\n<p>Plusieurs solutions se pr\u00e9sentent pour pouvoir r\u00e9duire tout de m\u00eame l&rsquo;espace disque occup\u00e9 :<\/p>\n<ul>\n<li>respecter la bonne pratique de mod\u00e9lisation consistant \u00e0 d\u00e9finir un index clustered pour l&rsquo;ensemble des tables. Remarque : ce n&rsquo;est pas parce qu&rsquo;on d\u00e9fini un index clustered que l&rsquo;on doit d\u00e9finir une cl\u00e9 primaire; l&rsquo;index clustered n&rsquo;est pas n\u00e9cessairement porteur d&rsquo;une contrainte d&rsquo;unicit\u00e9<\/li>\n<li>l&rsquo;autre solution consiste a compl\u00e9ter le plan de maintenance avec un petit script permettant de forcer la reconstruction des segments m\u00e9moire.<\/li>\n<\/ul>\n<p>La syntaxe pour reconstruire un segment m\u00e9moire ne s&rsquo;appuie pas sur la modification d&rsquo;un index (ALTER INDEX) mais se traduit directement par une op\u00e9ration sur la table (ALTER TABLE xxx REBUILD), comme d\u00e9taill\u00e9 <a title=\"ALTER TABLE\" href=\"https:\/\/docs.microsoft.com\/fr-fr\/sql\/t-sql\/statements\/alter-table-transact-sql\" target=\"_blank\" rel=\"noopener noreferrer\">ici<\/a>.<\/p>\n<p>Voici une proposition d&rsquo;op\u00e9ration compl\u00e9mentaire ajout\u00e9e au plan de maintenance, pour reconstruire tous les segments m\u00e9moire de toutes les bases de donn\u00e9es actives.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">sp_MSforeachdb '\r\nif exists (select * from sys.databases where name=''?'' and state=0 and database_id&gt;4)\r\nBEGIN\r\nUSE [?];\r\ndeclare @requete nvarchar(max)=N'''';\r\nselect @requete+=''ALTER TABLE '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(t.name) + '' REBUILD;''\r\n\tFROM sys.indexes i\r\n\t\tjoin sys.tables t on t.object_id=i.object_id\r\n\t\tjoin sys.schemas s on s.schema_id=t.schema_id\r\n\twhere i.index_id=0;\r\nexec (@requete)\r\nEND\r\n'<\/pre>\n<p>Il suffit donc de positionner ce script en compl\u00e9ment de la t\u00e2che standard de reconstruction des index, pour obtenir un r\u00e9sultat compl\u00e8tement satisfaisant.<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/03\/Propre.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1182\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/03\/Propre-620x74.png\" alt=\"Propre\" width=\"620\" height=\"74\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/03\/Propre-620x74.png 620w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/03\/Propre-300x36.png 300w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/03\/Propre-624x75.png 624w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2015\/03\/Propre.png 726w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p>Voici donc, sous forme d&rsquo;une petite progression simple, la d\u00e9monstration que la t\u00e2che par d\u00e9faut de reconstruction des index dans les plans de maintenance ne couvre pas n\u00e9cessairement l&rsquo;ensemble des cas possibles &#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>M\u00eame si certains oublient de les mettre en place (et s&rsquo;\u00e9tonnent ensuite de voir les performances de leur application se d\u00e9grader au fil du temps &#8230;), des plans de maintenance sont g\u00e9n\u00e9ralement\u00a0impl\u00e9ment\u00e9s\u00a0sur les serveurs SQL Server, et int\u00e8grent notamment une &hellip; <a href=\"https:\/\/www.sqlserver.fr\/blog\/plan-de-maintenance-et-tables-sans-index-clustered\/\">Continuer la lecture <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1161","post","type-post","status-publish","format-standard","hentry","category-article_sql"],"_links":{"self":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/1161","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/comments?post=1161"}],"version-history":[{"count":24,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/1161\/revisions"}],"predecessor-version":[{"id":1923,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/1161\/revisions\/1923"}],"wp:attachment":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/media?parent=1161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/categories?post=1161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/tags?post=1161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}