{"id":352,"date":"2012-03-18T00:00:52","date_gmt":"2012-03-18T00:00:52","guid":{"rendered":"http:\/\/www.sqlserver.fr\/blog\/?p=352"},"modified":"2026-05-02T14:31:17","modified_gmt":"2026-05-02T12:31:17","slug":"stockage-des-donnees-et-index-partie-3-index-nonclustered","status":"publish","type":"post","link":"https:\/\/www.sqlserver.fr\/blog\/stockage-des-donnees-et-index-partie-3-index-nonclustered\/","title":{"rendered":"Stockage des donn\u00e9es et index &#8211; Partie 3 : Index NonClustered"},"content":{"rendered":"<p>Apr\u00e8s avoir pr\u00e9sent\u00e9 dans deux pr\u00e9c\u00e9dents articles les <a href=\"https:\/\/www.sqlserver.fr\/blog\/stockage-des-donnees-et-index-partie-1-heap\/\" target=\"_blank\" rel=\"noopener noreferrer\">Heaps<\/a> et les <a href=\"https:\/\/www.sqlserver.fr\/blog\/stockage-des-donnees-et-index-partie-2-index-clustered\/\" target=\"_blank\" rel=\"noopener noreferrer\">Index Clustered<\/a>, ce dernier article de la s\u00e9rie va d\u00e9tailler ce qu\u2019est un index non clustered.<!--more--><br \/>\nA la diff\u00e9rence des index clustered, les index non clustered ne contiennent pas l\u2019ensemble des colonnes de la table mais seulement une ou plusieurs colonnes, constituant la cl\u00e9.<br \/>\nCet index est aussi stock\u00e9 sous forme d\u2019un arbre balanc\u00e9 (de m\u00eame que pour l\u2019<a href=\"https:\/\/www.sqlserver.fr\/blog\/stockage-des-donnees-et-index-partie-2-index-clustered\/\" target=\"_blank\" rel=\"noopener noreferrer\">index clustered<\/a>) et permet pour les diff\u00e9rentes valeurs de la cl\u00e9 d\u2019atteindre l\u2019endroit o\u00f9 sont stock\u00e9es l\u2019ensemble des colonnes, c\u2019est-\u00e0-dire le <a href=\"https:\/\/www.sqlserver.fr\/blog\/stockage-des-donnees-et-index-partie-1-heap\/\" target=\"_blank\" rel=\"noopener noreferrer\">Heap<\/a> ou l\u2019<a href=\"https:\/\/www.sqlserver.fr\/blog\/stockage-des-donnees-et-index-partie-2-index-clustered\/\" target=\"_blank\" rel=\"noopener noreferrer\">Index clustered<\/a>.<br \/>\nPour cela, dans les pages feuilles de l\u2019index non clustered, on retrouve les valeurs de la cl\u00e9 compl\u00e9t\u00e9es selon le cas :<\/p>\n<p style=\"padding-left: 30px;\">&#8211; du RowId de l\u2019enregistrement correspondant dans le Heap s\u2019il n\u2019existe pas d\u2019index clustered pour la table.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE TABLE TableSansClustered (Id int identity, Nom varchar(50), Prenom varchar(50), DateNaissance date, Commentaires varchar(MAX) DEFAULT 'Pas de commentaires')\r\n\r\nINSERT INTO TableSansClustered (Nom, Prenom, DateNaissance)\r\nVALUES ('DUPONT','Pierre','19871029'),\r\n\t\t('DURAND','C\u00e9dric','20010412'),\r\n\t\t('SCHMIDT','Paul','19990305')\r\n\r\nCREATE INDEX IX_NomPrenom ON TableSansClustered(Nom,Prenom)<\/pre>\n<p style=\"padding-left: 30px;\">Le contenu de la page feuille de cet index non clustered peut \u00eatre r\u00e9sum\u00e9 de la mani\u00e8re suivante :<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0011.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-353\" title=\"image001\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0011.png\" alt=\"\" width=\"535\" height=\"80\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0011.png 535w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0011-300x44.png 300w\" sizes=\"auto, (max-width: 535px) 100vw, 535px\" \/><\/a><\/p>\n<p style=\"padding-left: 30px;\">On voit donc pour chaque ligne la pr\u00e9sence des deux colonnes de la cl\u00e9 (Nom et Prenom) ainsi que du l\u2019identifiant de ligne (Row ID = RID) du segment de donn\u00e9es.<\/p>\n<p style=\"padding-left: 30px;\">&#8211; de la cl\u00e9 de l\u2019Index Clustered s\u2019il en existe un pour la table.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE TABLE TableAvecClustered (Id int identity PRIMARY KEY, Nom varchar(50), Prenom varchar(50), DateNaissance date, Commentaires varchar(MAX) DEFAULT 'Pas de commentaires')\r\n\r\nINSERT INTO TableAvecClustered (Nom, Prenom, DateNaissance)\r\nVALUES ('DUPONT','Pierre','19871029'),\r\n\t\t('DURAND','C\u00e9dric','20010412'),\r\n\t\t('SCHMIDT','Paul','19990305')\r\n\r\nCREATE INDEX IX_NomPrenom ON TableAvecClustered(Nom,Prenom)<\/pre>\n<p style=\"padding-left: 30px;\">Le contenu de la page feuille de cette index non clustered peut \u00eatre r\u00e9sum\u00e9 de la mani\u00e8re suivante :<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0021.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-354\" title=\"image002\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0021.png\" alt=\"\" width=\"452\" height=\"79\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0021.png 452w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0021-300x52.png 300w\" sizes=\"auto, (max-width: 452px) 100vw, 452px\" \/><\/a><\/p>\n<p style=\"padding-left: 30px;\">On voit pour chaque ligne que l\u2019on a la cl\u00e9 de l\u2019index non clustered et aussi la cl\u00e9 de l\u2019index clustered (dans notre cas la colonne Id, qui est Primary Key et par d\u00e9faut index clustered). A noter au passage que si l\u2019index clustered avait eu des colonnes en commun avec l\u2019index non clustered, seules les nouvelles colonnes auraient \u00e9t\u00e9 r\u00e9p\u00e9t\u00e9es.<\/p>\n<p>Lorsqu\u2019une requ\u00eate passe par l\u2019index non clustered, elle peut ainsi renvoyer l\u2019ensemble des colonnes de l\u2019index non clustered, plus le cas \u00e9ch\u00e9ant l\u2019ensemble des colonnes de l\u2019index clustered, sans avoir besoin d\u2019aller chercher plus loin.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">select Nom, Prenom\r\nFROM TableSansClustered\r\nwhere Nom='DURAND'<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0032.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-355\" title=\"image003\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0032.png\" alt=\"\" width=\"531\" height=\"118\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0032.png 531w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0032-300x66.png 300w\" sizes=\"auto, (max-width: 531px) 100vw, 531px\" \/><\/a><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">select Nom, Prenom, Id\r\nFROM TableAvecClustered\r\nwhere Nom='DURAND'<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0041.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-356\" title=\"image004\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0041.png\" alt=\"\" width=\"577\" height=\"129\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0041.png 577w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0041-300x67.png 300w\" sizes=\"auto, (max-width: 577px) 100vw, 577px\" \/><\/a><\/p>\n<p>Maintenant, si la requ\u00eate retourne une ou plusieurs colonnes qui ne font pas partie du contenu des feuilles de l\u2019index non cllustered, et si l\u2019on cherche n\u00e9anmoins \u00e0 forcer un passage par cet index, alors le moteur aura besoin d\u2019aller chercher les colonnes manquantes dans l\u2019index clustered s\u2019il existe ou \u00e0 d\u00e9faut dans le heap, c\u2019est-\u00e0-dire l\u00e0 o\u00f9 sont stock\u00e9es toutes les colonnes.<br \/>\nAinsi, on voit que pour la table sans index clustered, une op\u00e9ration de RowId lookup est n\u00e9cessaire pour, \u00e0 partir du RowId pr\u00e9sent dans les feuilles de l\u2019index clustered, obtenir le contenu de la colonne DateNaissance.<br \/>\n<a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image005.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-357\" title=\"image005\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image005.jpg\" alt=\"\" width=\"604\" height=\"141\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image005.jpg 604w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image005-300x70.jpg 300w\" sizes=\"auto, (max-width: 604px) 100vw, 604px\" \/><\/a><br \/>\nEt pour la table avec l\u2019index clustered, c\u2019est une op\u00e9ration de Key lookup qui permet, \u00e0 partir de la cl\u00e9 de l\u2019index clustered, d\u2019obtenir la colonne manquante.<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image006.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-358\" title=\"image006\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image006.jpg\" alt=\"\" width=\"604\" height=\"140\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image006.jpg 604w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image006-300x69.jpg 300w\" sizes=\"auto, (max-width: 604px) 100vw, 604px\" \/><\/a><\/p>\n<p>A noter depuis SQL Server 2005 la notion de colonne incluse permet d\u2019ajouter une ou plusieurs colonnes uniquement au niveau feuille de l\u2019index non clustered, sans que celles-ci n\u2019aient \u00e0 \u00eatre pr\u00e9sentes sur l\u2019ensemble de l\u2019arbre d\u2019index. C\u2019est notamment utile lorsqu\u2019une colonne prend trop d\u2019espace (longues cha\u00eenes de caract\u00e8res par exemple) ou est d\u2019un type de donn\u00e9es non triable (XML, type CLR, \u2026). On peut ainsi obtenir des index dits \u00ab couvrants \u00bb pour telle ou telle requ\u00eate, c\u2019est-\u00e0-dire que le contenu de leurs feuilles est suffisant pour obtenir l\u2019ensemble des colonnes requises pour satisfaire aux besoins de la requ\u00eate.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE INDEX IX_NomPrenom_DateNaissanceIncluse\r\nON TableAvecClustered(Nom,Prenom)\r\nINCLUDE (DateNaissance)<\/pre>\n<p>Cet index est couvrant pour la requ\u00eate suivante :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">select Nom, Prenom, Id,DateNaissance\r\nFROM TableAvecClustered\r\nwhere Nom='DURAND'<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image007.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-359\" title=\"image007\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image007.jpg\" alt=\"\" width=\"604\" height=\"102\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image007.jpg 604w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image007-300x50.jpg 300w\" sizes=\"auto, (max-width: 604px) 100vw, 604px\" \/><\/a><\/p>\n<p>Bien entendu, cette notion de colonne incluse n\u2019a pas de sens pour un index non-clustered, puisqu\u2019un tel index contient d\u00e9j\u00e0 par d\u00e9finition toutes les colonnes au niveau de ses feuilles.<\/p>\n<p>Enfin, SQL Server propose \u00e0 partir de sa version 2008 la notion d\u2019index filtr\u00e9, qui permet de sortir d\u2019un index les colonnes qui ne sont pas n\u00e9cessaires d\u2019un point de vue fonctionnel.<br \/>\nPar exemple, dans l\u2019exemple courant, on peut d\u00e9cider de s\u2019attacher uniquement aux personnes n\u00e9es le si\u00e8cle dernier.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">CREATE INDEX IX_20emeSiecle\r\nON TableAvecClustered(Nom,Prenom)\r\nWHERE DateNaissance&lt;'20000101'<\/pre>\n<p>La page feuille de cet index ne contient que les enregistrements correspondant aux personnes n\u00e9es le si\u00e8cle dernier :<\/p>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0081.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-360\" title=\"image008\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0081.png\" alt=\"\" width=\"463\" height=\"67\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0081.png 463w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image0081-300x43.png 300w\" sizes=\"auto, (max-width: 463px) 100vw, 463px\" \/><\/a><\/p>\n<p>Mais ces informations sont suffisantes pour les requ\u00eates ne ciblant que des enregistrements parmi ce sous-groupe.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">select Nom, Prenom, Id\r\nFROM TableAvecClustered\r\nWHERE DateNaissance&lt;'20000101'\r\nAND Nom='DUPONT'<\/pre>\n<p><a href=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image009.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-361\" title=\"image009\" src=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image009.jpg\" alt=\"\" width=\"604\" height=\"99\" srcset=\"https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image009.jpg 604w, https:\/\/www.sqlserver.fr\/blog\/wp-content\/uploads\/2012\/03\/image009-300x49.jpg 300w\" sizes=\"auto, (max-width: 604px) 100vw, 604px\" \/><\/a><\/p>\n<p>N\u2019h\u00e9sitez pas \u00e0 utiliser des index pour optimiser certaines de vos requ\u00eates de s\u00e9lection, mais gardez toutefois bien en t\u00eate qu\u2019ils sont maintenus en direct par le syst\u00e8me, et donc qu\u2019\u00e0 ce titre ils peuvent nettement ralentir les requ\u00eates de modification des donn\u00e9es. Il conviendra donc de trouver le juste milieu \u2026<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Apr\u00e8s avoir pr\u00e9sent\u00e9 dans deux pr\u00e9c\u00e9dents articles les Heaps et les Index Clustered, ce dernier article de la s\u00e9rie va d\u00e9tailler ce qu\u2019est un index non clustered.<\/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-352","post","type-post","status-publish","format-standard","hentry","category-article_sql"],"_links":{"self":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/352","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=352"}],"version-history":[{"count":26,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/352\/revisions"}],"predecessor-version":[{"id":1959,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/posts\/352\/revisions\/1959"}],"wp:attachment":[{"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/media?parent=352"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/categories?post=352"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlserver.fr\/blog\/wp-json\/wp\/v2\/tags?post=352"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}