Sie sind auf Seite 1von 336

ADMINISTRATION

EXPLOITATION

SQL SERVER
Dimensionnement, supervision, performances du moteur et du code SQL

OPTIMISER

Rudi Bruchez

OPTIMISER

SQL SERVER
Dimensionnement, supervision, performances du moteur et du code SQL

PHP 6 et MySQL 5 Crez des sites web dynamiques Larry Ullman 688 pages Dunod, 2008

Business Intelligence avec SQL Server 2005 Mise en uvre dun projet dcisionnel Bertrand Burquier 432 pages Dunod, 2007

Unicode 5.0 en pratique Codage des caractres et internationalisation des logiciels et des documents Patrick Andries 424 pages Dunod, 2008

OPTIMISER

SQL SERVER
Dimensionnement, supervision, performances du moteur et du code SQL

Rudy Bruchez
Consultant et formateur spcialis sur SQL Server MVP sur SQL Server Certifi Microsoft MCDBA, MCT et MCITP

Toutes les marques cites dans cet ouvrage sont des marques dposes par leurs propritaires respectifs.

Illustration de couverture : Parc du Torres del paine piccaya-Fotolia.com

Dunod, Paris, 2008 ISBN 978-2-10-053750-1

Table des matires

Avant-propos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Premire partie - Optimisation du systme Chapitre 1 Rgles de base de loptimisation . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 tapes de loptimisation ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Faut-il tout optimiser ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Maintenance dune baseline. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 2 Architecture de SQL Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1 Architecture gnrale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Structures de stockage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 Fichiers de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.2 Journal de transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.3 Taille des fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 SQLOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 3 Optimisation du matriel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Choix de larchitecture matrielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Processeur(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

VII

3 3 5 6 7 7 9 9 15 20 20 25 25 26

IV

Optimiser SQL Server

3.1.2 3.1.3 3.1.4 3.1.5

Mmoire vive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilisation de la mmoire vive par SQL Server . . . . . . . . . . . . . . . . . . . . . . Disques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Virtualisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

30 35 39 40 42 45 45 46 49 59 77 85 88 93 93 96

3.2 Configuration du serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 4 Optimisation des objets et de la structure de la base de donnes . 4.1 Modlisation de la base de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Terminologie du modle relationnel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.2 Normalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.3 Bien choisir ses types de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Partitionnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 tempdb. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4 Contrle de lattribution des ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 5 Analyse des performances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1 SQL Server Management Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 SQL Trace et le profiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5.2.1 Utiliser le rsultat de la trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 5.2.2 Diminution de limpact de la trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 5.3 Moniteur systme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 5.4 Choix des compteurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 5.4.1 5.4.2 5.4.3 5.4.4 5.4.5 5.4.6 Compteurs essentiels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Compteurs utiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Compteurs pour tempdb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Compteurs utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Identification de la session coupable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utiliser les compteurs dans le code SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 119 127 128 128 129

5.5 Programmation de journaux de compteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 5.6 Programmation dalertes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 5.7 Corrlation des compteurs de performance et des traces . . . . . . . . . . . . . . . . 134 5.8 vnements tendus (SQL Server 2008) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

Table des matires

5.8.1 Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.8.2 Cration dune session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.9 Outils de supervision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Deuxime partie - Optimisation des requtes Chapitre 6 Utilisation des index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 Principes de lindexation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 6.1.2 6.1.3 6.1.4 Index clustered . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Choix de lindex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cration dindex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optimisation de la taille de lindex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

136 137 139

147 147 150 159 164 171 176 177 180 181 183 185 186 187 187 193 196 207 207 209 210 214 218 227 230 230 235

6.2 Vues de gestion dynamique pour le maintien des index . . . . . . . . . . . . . . . . 6.2.1 Obtention des informations oprationnelles de lindex. . . . . . . . . . . . . . . . . 6.2.2 Index manquants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Vues indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4 Statistiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 Statistiques sur les index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Colonnes non indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Slectivit et densit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Consultation des statistiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Maintien des statistiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6.5 Database Engine Tuning Advisor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 7 Transactions et verrous. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1 Dfinition dune transaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 Verrouillage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.1 Modes de verrouillage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.2 Granularit de verrouillage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3 Niveaux disolation de la transaction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4 Attentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5 Blocages et deadlocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5.1 Dtection des blocages par notification dvnements . . . . . . . . . . . . . . . . . 7.5.2 Verrous mortels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

VI

Optimiser SQL Server

Chapitre 8 Optimisation du code SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 8.1 Lecture dun plan dexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 8.1.1 Principaux oprateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 8.1.2 Algorithmes de jointure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 8.2 Gestion avance des plans dexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 8.2.1 Indicateurs de requte et de table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 8.2.2 Guides de plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260 8.3 Optimisation du code SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262 8.3.1 8.3.2 8.3.3 8.3.4 Tables temporaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pour ou contre le SQL dynamique. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viter les curseurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optimisation des dclencheurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270 272 276 281

Chapitre 9 Optimisation des procdures stockes . . . . . . . . . . . . . . . . . . . . . . 287 9.1 done_in_proc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 9.2 Matrise de la compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 9.2.1 Paramtres typiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 9.2.2 Recompilations automatiques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 9.3 Cache des requtes ad hoc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 9.3.1 Rutilisation des plans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 9.3.2 Paramtrage du SQL dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 9.4 propos du code .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 Bibliographie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317

Avant-propos

Avez-vous bonne mmoire ? Lorsque, dans une conversation, vous devez citer des chiffres, une anecdote lue dans le journal, prononcer ladjectif qui dcrit prcisment votre pense, y parvenez-vous sans hsiter ? Si vous y arrivez la plupart du temps de manire satisfaisante, ny a-t-il pas, parfois, des mots qui vous chappent, des informations qui vous restent sur le bout de la langue, jusqu ce que vous vous en souveniez subitement, lorsquil est bien trop tard ? Parfois, votre mmoire est plus quun outil de conversation : lorsque vous devez retrouver votre chemin dans les rues dune mtropole, ou vous souvenir sil faut couper le fil bleu ou le fil rouge pour dsamorcer une bombe prte exploser Mme si vous ntes pas souvent confront esprons-le ce dernier cas, vous comprenez que parfois, accder vos souvenirs rapidement, de faon fluide, sans -coups, est une ncessit. Un systme de gestion de bases de donnes relationnelles (SGBDR) est comme une mmoire : il contient des donnes importantes, sinon vitales, pour lentreprise, et la capacit donne aux acteurs de cette entreprise de pouvoir y accder efficacement, rapidement, avec des temps de rponse consistants, est essentielle. Le problme des performances des bases de donnes sest pos ds leur apparition. Labstraction btie entre le stockage physique des donnes et leur reprsentation logique travers diffrents modles le modle relationnel tant aujourdhui prdominant ncessite de la part des diteurs de ces logiciels un trs important travail de dveloppement. Il faut dabord traduire les requtes exprimes dans des langages dclaratifs comme le SQL (Structured Query Language) en stratgies de recherche de donnes optimales, puis parvenir stocker les informations de la faon la plus favorable leur parcours et leur extraction. A priori, rien dans la norme SQL ni dans la

VIII

Optimiser SQL Server

thorie relationnelle ne concerne les performances. Cest une proccupation qui ne peut venir quensuite, comme on ne peint les faades que lorsque la maonnerie est termine. Toutefois, les performances ne font pas partie de la faade, mais plutt du moteur de lascenseur, et sont donc notre proccupation quotidienne. Le modle relationnel et le langage SQL sont des existants notre disposition, notre SGBDR est install et fonctionnel, notre responsabilit, en tant quadministrateur de bases de donnes, dveloppeur ou responsable IT, est den assurer le fonctionnement optimal. Pour cette tche, il ny a pas de norme, de commandements gravs dans la pierre, doutils ou de modles prexistants qui vont effectuer le travail pour nous. Loptimisation est une proccupation constante, une tche toujours continuer, modifier, complter. Cest aussi une forme dartisanat, qui ncessite de bonnes connaissances du systme, de la curiosit et de la patience.

Concrtement
Optimiser, cela veut dire concrtiser notre connaissance de la thorie, dans la pratique, cest--dire prendre en compte la faon dont cette thorie a t mise en uvre dans une implmentation particulire. Cet ouvrage est ddi loptimisation pour SQL Server 2005 et 2008, le SGBDR de Microsoft. Les deux versions ne prsentant pas dnormes diffrences de ce point de vue, la plupart des informations contenues dans ce livre peuvent sappliquer indiffremment aux deux versions. Nous indiquerons dans le corps de louvrage lorsque telle ou telle fonctionnalit est propre une version. Nous y aborderons tous les points de loptimisation (matriel, configuration, requtes SQL), dun point de vue essentiellement pratique, tout en vous prsentant ce qui se passe sous le capot , connaissance indispensable pour vous permettre de mettre en uvre efficacement les mthodes proposes. Notre objectif est de vous guider travers les outils, concepts et pratiques propres loptimisation des performances, pour que vous puissiez, concrtement et de faon autonome, non seulement btir un systme performant, mais aussi rsoudre au quotidien les problmes et lenteurs qui pourraient survenir. Loptimisation, le tuning sont des pratiques quotidiennes : ce livre cherche donc vous donner tous les bons outils et les bons rflexes pour vous permettre dtre efficace jour aprs jour.

qui sadresse ce livre ?


Il sagit donc dun livre essentiellement pratique. Il sadresse toute personne dveloppeur, DBA (administrateur de bases de donnes), consultant, professionnel IT qui doit sassurer du fonctionnement optimal de SQL Server. Nous ne prsenterons pas les bases thoriques des SGBDR, et ce livre ne contient pas dinitiation SQL Server. Loptimisation tant un sujet plus avanc, nous partons donc du principe que vous tes dj familier avec les bases de donnes, et spcifiquement SQL Server. Pour profiter de toute la partie ddie loptimisation du code SQL, vous devez connatre au moins les bases du langage SQL et de ses extensions Transact-SQL (utilisation de variables, structures de contrle).

Avant-propos

IX

Loptimisation recouvre plusieurs domaines, qui peuvent tre clairement dlimits dans votre entreprise, ou non. Les parties de ce livre dveloppent les diffrents lments doptimisation prendre en compte. Si vous vous occupez exclusivement dadministration et dinstallation matrielles, la premire partie, Optimisation du systme , traite du matriel et de la configuration. Elle prsente larchitecture de SQL Server et les principes de loptimisation, et jette donc des bases thoriques pour aborder les parties pratiques. Si vous tes principalement dveloppeur et que votre souhait est dcrire du code SQL plus performant, la seconde partie, Optimisation des requtes , vous est ddie. Elle traite non seulement du code SQL lui-mme, mais des mcanismes transactionnels pouvant provoquer des attentes et des blocages, et de lutilisation dindex pour offrir les meilleures performances possibles.

Lauteur
Rudi Bruchez travaille avec SQL Server depuis 1998. Il est aujourdhui expert indpendant spcialis en SQL Server, bas Paris. Il est certifi MCDBA SQL Server 2000 et MCITP SQL Server 2005. Il est galement MVP (Most Valuable Professional) SQL Server depuis 2008. Dans son activit de consultant et formateur, il rpond quotidiennement toutes les problmatiques touchant aux parties relationnelles et dcisionnelles de SQL Server, notamment en modlisation, administration et optimisation.

Vous trouverez une page ddie aux ventuels errata de ce livre sur www.babaluga.com le site de lauteur. Vous pouvez galement consulter des articles concernant SQL Server sur rudi.developpez.com. Lauteur rpond rgulirement sur le forum SQL Server de developpez.net et sur le newsgroup microsoft.public.fr.sqlserver.

Termes utiliss
Pour viter les rptitions excessives, nous utiliserons les abrviations courantes dans le monde SQL Server, et le monde des systmes de gestion de bases de donnes relationnelles (SGBDR). En voici la liste : BOL (Books Online) : laide en ligne de SQL Server. SSMS (SQL Server Management Studio) : loutil client dadministration et de dveloppement de SQL Server. RTM (Release to manufacturing) : version stable, en premire livraison, avant la sortie de service packs. SP (Service Pack) : mise jour importante dune version, comportant des correctifs et des amliorations. VLDB (Very Large DataBase) : base de donnes de trs grande volumtrie. CTP (Common Technology Preview) : versions de pr-sortie de SQL Server, livres bien avant la sortie officielle du produit, et contenant uniquement les fonctionnalits implmentes compltement ce moment.

Optimiser SQL Server

OLTP (Online Transactional Processing) : une utilisation particulire des donnes, principalement transactionnelle : critures constantes, lectures de petits volumes. Ce sont les bases de donnes oprationnelles traditionnelles. OLAP (Online Analytical Processing) : une utilisation dun entrept de donnes dans un but danalyse. Ce qu'on nomme parfois lutilisation dcisionnelle, ou la Business Intelligence. Les bases de donnes OLAP sont utilises principalement en lecture, pour des extractions massives. MSDN (Microsoft Developer Network) : documentation et articles en ligne sur Internet ou sur CD-ROM/DVD, reprsentant une base de connaissance trs fournie sur les produits Microsoft, destination principalement des dveloppeurs. Accs libre sur http://msdn.microsoft.com/ Drapeau de trace (Trace flags) : switches numriques, qui permettent de modifier le comportement de SQL Server. On les utilise comme paramtres au lancement du service, ou on les active/dsactive chaud, laide des commandes DBCC TRACEON et DBCC TRACEOFF.

Exemples de code
Le code contenu dans cet ouvrage est disponible sur la page ddie louvrage sur le site des ditions Dunod, www.dunod.com, et sur le site de lauteur, www.babaluga.com Il est fond sur la base de donnes dexemple de Microsoft, nomme AdventureWorks. Elle reprsente une entreprise fictive du mme nom qui vend des bicyclettes. Vous pouvez tlcharger AdventureWorks pour SQL Server 2005 ou SQL Server 2008 ladresse : http://www.codeplex.com/MSFTDBProdSamples. Nous utiliserons souvent en exemple la table Person.Contact, elle contient 19 972 lignes de contacts, avec des colonnes simples comme FirstName, LastName et EmailAddress.

Utilisation de langlais
Nous avons fait le choix dutiliser une version anglaise de SQL Server, principalement parce que, lheure o nous rdigeons ce livre, SQL Server 2008 nest pas encore sorti en version finale, et nexiste donc quen anglais. De plus, certaines traductions de linterface franaise sont plus troublantes quutiles. Nous indiquerons parfois en regard le terme franais. La correspondance nest pas difficile faire avec une version franaise : les entres de menus et la disposition dans les botes de dialogue sont les mmes.

Remerciements
Merci Frdric BROUARD, MVP SQL Server, fondateur de la socit SQL Spot, pour ses conseils et suggestions et sa relecture. Merci galement Pascale DOZ, experte indpendante, pour sa relecture et son aide. Merci Christian ROBERT, MVP SQL Server, pour ses astuces. Et enfin, merci galement Edgar Frank CODD pour avoir tout invent.

PREMIRE PARTIE

Optimisation du systme
Un SGBDR comme SQL Server, a se soigne. Vous linstallez confortablement sur une machine adapte, vous le laissez spanouir, stendre son aise dans un mobilier choisi, vous le nourrissez de modles de donnes bien structurs, vitamins, surtout pas trop gras, vous lajustez pour quil donne le meilleur de lui-mme, enfin, vous surveillez ses signes vitaux de faon proactive, pour lui garantir une vie longue et efficace. Ce sont tous ces lments que nous allons dtailler ici.

1
Rgles de base de loptimisation

Objectif
Que veut dire optimiser ? Ce court chapitre prsente les bonnes pratiques et la dmarche logique qui encadrent loptimisation dun systme informatique, il vous invite inclure la dmarche doptimisation dans lapproche globale de votre projet de base de donnes, den faire une composante justement dose et toujours en volution.

1.1 TAPES DE LOPTIMISATION ?


Que pouvons-nous, et que faut-il optimiser ? Ce qui vient le plus souvent lesprit, est ce qui est appel en termes gnriques la configuration. Configuration matrielle, cest--dire les lments de hardware constituant un serveur, comme configuration logicielle, cest--dire les paramtres du systme dexploitation et du SGBDR luimme. Bien quils soient importants, ces lments ne constituent que des tapes pour assurer un fonctionnement optimal. Dautres aspects, souvent ngligs, sont cruciaux, et nous nous efforcerons de vous les prsenter en dtail pour vous permettre de tirer le meilleur parti de vos bases de donnes. Il ne sagit pas seulement de penser performance lors de linstallation du serveur, mais ds la phase de conception de la structure des donnes, jusqu lcriture du code.

Chapitre 1. Rgles de base de loptimisation

La rflexion sur les performances doit donc prendre place ds les premiers moments du projet, et durant toute son volution : modlisation, choix du matriel, installation, organisation du stockage physique et logique (une bonne indexation des tables est naturellement dterminante), codage SQL, supervision du serveur Dans les faits, malheureusement, cette rflexion est trop souvent nglige, et le besoin doptimisation merge en bout de chane, lorsque le systme est en place et que les temps de rponse, soudainement ou progressivement, ne donnent plus satisfaction. Parfois, laugmentation du volume de donnes ou du nombre dutilisateurs simultans a ralenti le temps dexcution des requtes de faon inacceptable. Vous vous retrouvez alors en face dun constat, et dans lobligation urgente de remdier au problme. Cette situation nest pas favorable pour effectuer un travail en profondeur, et dans le bon sens, mais cest malheureusement la situation la plus frquente, lorsquune planification prenant en compte les performances na pas t mene depuis le dbut du projet. Nous prsenterons dans cet ouvrage tous les outils qui permettent didentifier la source des ralentissements et dy remdier avec prcision. Pour autant, lurgence ne doit pas vous pousser rpondre trop rapidement la pression, et choisir les solutions trop videntes, celles qui sont maintenant presque des lieux communs de linformatique, avec en tte, la mise jour du matriel. Augmenter la puissance du matriel est trop souvent une premire rponse facile, un pis-aller bas sur un bon sens erron. Combien de programmeurs en C faut-il pour changer une ampoule ? Un seul suffit, bien entendu. Il ne sert rien de changer une ampoule plusieurs, mme si la blague en rajoute cinq de plus, qui seront ncessaires six mois plus tard pour comprendre lalgorithme. De mme pour SQL Server. Les gains de performance ne seront pas automatiquement obtenus par un matriel plus puissant, car bien souvent, le problme provient dune mauvaise architecture de la base de donnes, dun manque dindex, de requtes peu optimales toutes sources de problmes que laugmentation de la puissance du matriel ne pourra pallier que trs faiblement. On ne peut donc optimiser au hasard. Pour amliorer les performances, nous ne pouvons faire lconomie de la recherche des causes. Cette recherche demande des outils appropris qui sont fort heureusement livrs avec SQL Server et un peu dobstination. Souvent, donc, loptimisation est urgente. Une base de donnes servant par exemple un site web marchand, ne peut se permettre un ralentissement prolong. Lorsque les performances chutent, les sirnes sont dclenches et les quipes entrent en mode durgence. Souvent, la premire solution qui vient lesprit est de relancer la machine, ce qui, empiriquement, amliore souvent la situation, notamment lorsque celui-ci provient dun problme de blocage de verrous. Si cela peut, ou pas, rgler rapidement le problme, il est certain que cela ne donne aucune information concrte sur la source du ralentissement. Vouloir trop rapidement rsoudre le problme nuit aux performances sur le long terme, car cela empche de mener lenqute ncessaire lidentification des causes, donc la mise en place dune solution adap-

1.2 Faut-il tout optimiser ?

te, prenne. Souvent, les problmes non rsolus la racine saccumulent pour provoquer une sorte de situation durgence permanente o toute lattention des techniciens est porte la rsolution en temps rel des problmes, comme des mdecins urgentistes. En optimisation, il faut savoir diffrer ladministration du mdicament, pour sassurer de bien comprendre la cause. Il est indispensable de prendre du recul, et ne pas travailler dans la prcipitation. Cette recherche de la cause se fait ncessairement chaud, lorsque le systme souffre de lenteur, tout comme un mdecin ne peut identifier une maladie quen auscultant le patient lorsquil est souffrant, et pas aprs sa gurison. Elle peut prendre un peu de temps, mais cette priode douloureuse permettra den viter bien dautres plus tard.

1.2 FAUT-IL TOUT OPTIMISER ?


Faut-il privilgier exclusivement les performances par rapport tout autre critre ? videmment non. Il est important de maintenir un quilibre entre simplicit, lisibilit et performance. Loptimiseur de requte de SQL Server est un des meilleurs du march, et accomplit en gnral un excellent travail. Il est souvent inutile de suroptimiser vos requtes, spcialement quand, dune syntaxe lautre, le plan de requte gnr par loptimiseur est identique. De mme, loptimisation ne doit pas devenir une obsession, car dans ce cas, le temps qui lui est ddi peut devenir exagr. Ici comme ailleurs, la loi de Pareto sapplique : 20 % des efforts doptimisation bien choisis permettent 80 % des gains de performance. Les 80 % restants peuvent coter beaucoup deffort sans gnrer de rsultats proportionnels. Prenons un exemple. La collation dtermine pour un serveur ou une base de donne, lordre de classement des colonnes de type chane de caractres. Le choix dune collation a un impact sur les performances de requtes lorsquelles effectuent des comparaisons ou des recherches de chanes. Une collation sensible la casse, ou plus forte encore de type binaire par exemple amliore la rapidit de ces requtes, car la comparaison de chane peut seffectuer directement sur les octets sans avoir besoin dutiliser une table dquivalences. Cest donc un chemin doptimisation possible, mais le jeu en vaut-il la chandelle, en sachant les contraintes fonctionnelles que cela implique ? Dans une base de donnes en collation binaire, non seulement toutes les requtes devront mentionner le nom de tous les objets (tables, colonnes) avec la casse correcte, mais toutes les recherches de chanes de caractres sur les colonnes dans cette collation seront sensibles la casse. tes-vous prt supporter ce surplus de contrainte pour une optimisation de performance certes intressante mais pas toujours vitale ? Cest un quilibre quil vous appartient de juger.

Chapitre 1. Rgles de base de loptimisation

1.3 MAINTENANCE DUNE BASELINE


Afin de juger correctement des performances de votre systme, vous devez bien le connatre. Lorsquun utilisateur final de votre base de donnes vous signale des problmes de performance, comment pouvez-vous savoir si les lenteurs reportes sont exceptionnelles, ou normales ? Vous navez quun moyen : btir, lavance une situation de rfrence ou baseline, et la maintenir au fil du temps. Une baseline est simplement la collecte sur une priode caractristique des compteurs importants qui permettent de connatre le comportement dun systme en temps normal. En journalisant rgulirement ces indicateurs, vous aurez une ide claire de la constitution physique de vos serveurs, comme vous pouvez dire pour vous-mme si vous tes fatigu ou de bonne humeur, parce que vous avez appris vous connatre au fil du temps. Il sagit aussi bien de recueillir les donnes de la charge matrielle (CPU, mmoire, rseau, disques), de lutilisation du systme dexploitation (processus consommateurs de ressources, temps privilgi et temps utilisateur) que de SQL Server lui-mme (nombre de transactions et de requtes par seconde, comportement des caches). Nous reviendrons sur les aspects pratiques de cette baseline lorsque nous aurons abord les outils permettant de collecter ces informations.

2
Architecture de SQL Server

Objectif
Afin dobtenir de bonnes performances, il est naturellement important de connatre le logiciel utilis. SQL Server est un systme de gestion de bases de donnes relationnelles (SGBDR) qui la fois respecte les standards de ce type doutils, et offre des implmentations particulires. Nous allons voir sur quelles briques logicielles SQL Server est bti, ce qui permettra ensuite de mettre en place des solutions doptimisation en toute connaissance de cause.

2.1 ARCHITECTURE GNRALE


SQL Server est un systme de gestion de base de donnes relationnelles (SGBDR). Il partage avec ses concurrents une architecture standardise, fruit de dcennies de recherches en ingnierie logicielle, menes par des chercheurs comme Edgar F. Codd, bien sr, Chris Date et Peter Chen pour le modle relationnel, mais aussi Michael Stonebreaker et dautres pour diffrentes mthodologies et algorithmes. Un SGBDR est en gnral une application client-serveur : un serveur centralis abrite le SGBDR lui-mme, dont la responsabilit est dassurer le stockage des donnes, et de rpondre aux demandes des clients.

Chapitre 2. Architecture de SQL Server

Figure 2.1 Architecture client-serveur

Dans le schma de la figure 2.1 extrmement simplifi, nous avons un rsum de larchitecture client-serveur de SQL Server. Une application cliente (comme SQL Server Management Studio SSMS) envoie des requtes en SQL. Lunique manire dobtenir des donnes, est de passer par le langage SQL. SQL Server nest pas un serveur de fichiers : il stocke et protge ses donnes laide de ses mcanismes internes, et noffre aucun moyen daccder directement aux donnes des bases. La seule faon dobtenir des lignes de table, est den faire la demande au serveur, laide dordres SQL. Ces ordres sont transmis par une bibliothque cliente, comme ODBC, OLEDB ou ADO.NET, dont la tche est de faire la transition entre les langages procduraux clients et les bibliothques de bas niveau en offrant une couche dabstraction. Cette bibliothque adresse la requte une bibliothque rseau (net-library) adapte aux protocoles rseaux utiliss (de nos jours, principalement TCP-IP), qui elle-mme la transmet par paquets travers la couche physique code dans le protocole rseau de SQL Server, nomm TDS (Tabular Data Stream), un protocole initialement dvelopp par Sybase1 et que SQL Server partage avec ce SGBDR (bien que limplmentation particulire de Microsoft soit nomme MS-TDS2). Du ct serveur, SQL Server est compos de deux principaux moteurs, respectivement le moteur relationnel, relational engine (ou moteur de requte, query engine), et le moteur de stockage, storage engine, qui contiennent chacun diffrents modules. La requte provenant du client est prise en charge par le moteur relationnel, qui value le code SQL, vrifie les mtadonnes et les privilges, passe par une phase doptimisation pour produire un plan dexcution (query plan) optimis, et gre
1. Jusqu la version 6.5 comprise, SQL Server tait un dveloppement partag entre Sybase et Microsoft. La version 7 fut totalement rcrite par Microsoft. 2. Le protocole MS-TDS est maintenant dcrit dans le MSDN : http://msdn.microsoft.com/enus/library/cc448436.aspx

2.2 Structures de stockage

lexcution de la requte. Les ordres dextraction ou dcriture proprement dits sont envoys au moteur de stockage, dont la responsabilit est, outre de lire et crire les pages de donnes, de grer aussi la cohrence transactionnelle, notamment travers le verrouillage. Toute la communication entre SQL Server et le serveur physique sur lequel il est install, est prise en charge par une couche dabstraction nomme SQLOS (OS pour Operating System), une sorte de systme dexploitation intgr au moteur, dont nous reparlerons plus loin dans ce chapitre. La sparation des rles entre moteur relationnel et moteur de stockage est une constante dans les architectures des SGBDR, certains permettent mme le choix entre diffrents moteurs de stockage, comme MySQL.

Nomenclature SQL Server Il est utile de spcifier ici les termes utiliss dans le cadre dun serveur SQL Server. Il est par exemple noter que les diffrents termes peuvent sembler troublants pour un dveloppeur habitu Oracle, car la nomenclature dOracle est sensiblement diffrente de celle de SQL Server. Un serveur SQL est un service Windows, qui est appel une instance. Ainsi, chaque installation de SQL Server sur une mme machine cre une nouvelle instance. Une seule instance par dfaut est possible par serveur : les autres instances sont appeles instances nommes . Il est ainsi possible dinstaller des versions et des ditions diffrentes sur la mme machine. Ensuite, une mme instance peut comporter un grand nombre de bases de donnes, qui sont fortement isoles les unes des autres, notamment du point de vue de la scurit. Ces bases de donnes contiennent des schmas, qui sont des conteneurs logiques conformes la norme SQL, et ne sont en rien lis des structures physiques de stockage ou des utilisateurs de la base. Un objet tel quune table, une vue, une procdure stocke, etc. appartient un et un seul schma.

2.2 STRUCTURES DE STOCKAGE


Le dfi des SGBDR comme SQL Server est dassurer les meilleures performances possibles lors de lcriture et de la lecture de larges volumes de donnes. Pour ce faire, chaque diteur soigne son moteur de stockage avec dautant plus de srieux que laccs aux disques durs, quelles que soient leurs spcifications, est llment le plus lent dun systme informatique. Pour assurer ce stockage de faon optimale, SQL Server utilise deux types de fichiers diffrents : fichiers de donnes et fichiers de journal de transaction.

2.2.1 Fichiers de donnes


Les fichiers de donnes offrent un stockage structur organis selon un modle schmatis dans la figure 2.2. Un groupe de fichiers peut contenir un ou plusieurs fichiers, qui eux-mmes contiennent des pages de donnes ou dindex, regroups dans des extensions (extents).

10

Chapitre 2. Architecture de SQL Server

Figure 2.2 Organisation du stockage dans un fichier de donnes.

Le groupe de fichiers est un contenu logique, qui fonctionne comme destination implicite ou explicite dobjets (partitions, tables, index). Lorsque vous crez une table ou un index, vous pouvez choisir le groupe de fichiers dans lequel il sera stock. Une base de donnes comporte au minimum un groupe de fichiers, cr automatiquement au CREATE DATABASE. Ce groupe sappelle PRIMARY, et il recueille les tables de catalogue (tables systme de mtadonnes). Il ne peut donc pas tre supprim. Chaque base possde un groupe de fichiers par dfaut dans lequel sera cr tout objet si aucun autre groupe de fichiers nest explicitement spcifi dans lordre de cration. Ce groupe de fichiers par dfaut peut-tre chang, avec la commande :
ALTER DATABASE nom_de_la_base MODIFY FILEGROUP [nom_du_groupe_de_fichiers] DEFAULT;

Les commandes pour connatre le groupe de fichiers par dfaut sont :


SELECT * FROM sys.filegroups WHERE is_default = 1; -- ou SELECT FILEGROUPPROPERTY('PRIMARY', 'IsDefault')

Un groupe de fichiers peut comporter un ou plusieurs fichiers. Il sagit simplement de fichiers disque, qui indiquent lemplacement physique du stockage. Si le groupe de fichiers est multifichiers, SQL Server y gre les critures la manire dun RAID 1 logiciel, en simple miroir. Le contenu est donc rparti plus ou moins galement entre les diffrents fichiers. Les objets (tables et index) sont donc attribus des groupes de fichiers. La rpartition des donnes seffectue dans des pages de 8 Ko, composes dun en-tte de

2.2 Structures de stockage

11

96 octets et dun corps contenant des lignes de table ou des structures dindex. Une page peut stocker 8 060 octets de donnes (8192 96 un peu de place laisse par scurit). La page est lunit minimale de lecture/criture de SQL Server. En dautres termes, lorsque le moteur de stockage crit ou lit sur le disque, il ne peut crire ou lire moins quune page. Limpact sur les performances est simple : plus vos lignes sont compactes (peu de colonnes, avec un type de donnes bien choisi), plus elles pourront rsider dans la mme page, et moins le nombre ncessaire de pages pour conserver la table sera lev.

Toutes les informations dentres-sorties retournes par le moteur de stockage (notamment dans le cas de rcuprations de statistiques IO via la commande SET STATISTICS IO ON de la session1) sont exprimes en pages. Cette commande de session permet laffichage de statistiques dentres/sorties aprs chaque instruction SQL. Elle est trs utile pour juger du cot dune requte. Nous en dtaillons lutilisation la section 5.1.

Donc, SQL Server lit une page, pas moins, et une page contient une ou plusieurs lignes, mais une ligne doit rsider tout entire dans la page. Cela signifie que la somme de la taille ncessaire au stockage de chaque colonne ne peut dpasser 8 060 octets, sauf exceptions qui suivent. Que se passe-t-il si la taille dune ligne dpasse lgrement la moiti de la taille de la page ? Tout fait comme lorsque vous essayez de placer dans une bote deux objets qui ny tiennent pas ensemble : vous devez acheter une seconde bote, et y placer un objet dans chacune, laissant une partie de lespace de la bote inutilise. Cest ce quon appelle la fragmentation interne, et cela peut donc constituer une augmentation de taille de stockage non ngligeable sur les tables volumineuses, ainsi que diminuer les performances de lectures de donnes. Les extensions permettent de grer lallocation de lespace du fichier. Elles contiennent 8 pages contigus, et sont donc dune taille de 64 Ko. Pour ne pas laisser trop despace inutilis, SQL Server partage les petites tables dans une mme extension, appele extension mixte . Lorsque la taille de la table dpasse les 8 pages, de nouvelles extensions (extensions uniformes) sont alloues uniquement cette table. Un dsordre des pages dans les extensions entrane une fragmentation externe. Comme un ordre particulier des lignes dans les pages, et des pages dans les extensions nont de sens que pour une table ordonne par un index clustered, nous traiterons des problmatiques de fragmentation dans le chapitre consacr aux index. Un fichier de donnes tant compos de pages, et seulement de pages, elles ne stockent pas uniquement le contenu de tables et dindex, bien que ce soit de trs loin leur utilisation la plus frquente. Quelques autres types existent, que vous pourrez
1. Session est le terme officiel pour une connexion utilisateur.

12

Chapitre 2. Architecture de SQL Server

voir apparatre de temps en temps, principalement dans les rsultats de requtes sur les vues de gestion dynamique. Vous trouvez leur description dans les BOL, sous lentre Pages and Extents. Ce sont principalement des pages de pointage et dinformation sur les structures : SPF, IAM, GAM, SGAM. Elles couvrent un certain nombre de pages ou dtendues (cette quantit tant nomme un intervalle). Les pages SPF couvrent 8 088 pages (environ 64 Mo), les pages GAM, SGAM et IAM couvrent 64 000 extensions (environ 4 Go). Sans entrer dans les dtails1, voici leur signification : SPF (Page Free Space) : contient un octet dinformation par page rfrence, indiquant notamment si la page est libre ou alloue, et quel est son pourcentage despace libre. GAM (Global Allocation Map) : contient un bit par extension, qui indique si elle est libre (1) ou dj alloue (0). SGAM (Shared Global Allocation Map) : contient un bit par extension, qui indique si elle est une extension mixte qui a au moins une page libre (1), ou si elle est une extension uniforme, ou qui na pas de page libre (0). IAM (Index Allocation Map) : indique quelles pages appartiennent un objet dans un intervalle GAM. La combinaison des informations contenues dans les pages de GAM, SGAM, IAM et SPF permet SQL Server de grer au mieux lallocation des pages et extensions dans le fichier de donnes.

Affichage du contenu des pages


La structure dtaille des pages et du stockage des index et des tables lintrieur nest en gnral pas dterminant pour les performances. Nous reviendrons sur certains aspects importants au fil de louvrage. Sachez toutefois que vous pouvez inspecter le contenu des pages laide dune commande non documente : DBCC PAGE. Elle est utile pour se rendre compte de la faon dont travaille le moteur de stockage, et pour exprimenter certains aspects du fonctionnement de SQL Server. Nous utiliserons cette commande dans le livre, notamment au chapitre 6. La syntaxe de DBCC PAGE est la suivante :
DBCC PAGE ( {'dbname' | dbid}, filenum, pagenum [, printopt={0|1|2|3} ])

o filenum est le numro de fichier et pagenum le numro de page, que vous trouvez dans dautres commandes en gnral non documentes, qui vous retourne un pointeur vers une page dans le format #:#. Par exemple 1:543 veut dire la page 543 dans le fichier 1. Printopt permet dindiquer le dtail dinformations retourner : 0 en-tte de la page. 1 contenu dtaill incluant la table doffset (row offset array, ou offset table, ladresse des lignes dans la page).
1. Disponibles par exemple sur le blog de Paul Randal : http://blogs.msdn.com/sqlserverstorageengine/

2.2 Structures de stockage

13

2 comme un, la diffrence que le contenu est retourn en un dump hexadcimal. 3 retourne len-tte, plus le contenu de la page en format tabulaire (jeu de rsultat) pour les index, ou dtaill pour le contenu de tables. Le rsultat de DBCC PAGE est envoy par dfaut dans le journal derreur de SQL Server (le fichier errorlog, quon trouve dans le rpertoire de donnes de SQL Server, dans le sous-rpertoire LOG). Pour retourner le rsultat dans la session, vous devez activer le drapeau de trace1 3604 :
DBCC TRACEON (3604)

DBCC PAGE supporte aussi loption WITH TABLERESULTS, qui vous permet de retourner le rsultat en format table. Le rsultat dune commande DBCC nest pas ais manipuler par code, une syntaxe telle que INSERT INTO DBCC nest pas prise en charge. Pour insrer le rsultat dune commande DBCC dans une table (temporaire par exemple), la seule solution est de passer par du SQL dynamique (cest--dire de passer la commande DBCC dans une chane de caractres et de lexcuter par un EXECUTE), et de profiter de la syntaxe INSERT INTO EXECUTE (). Par exemple :
CREATE TABLE #dbccpage (ParentObject sysname, Object sysname, Field sysname, Value sysname) GO INSERT INTO #dbccpage EXEC ('DBCC PAGE (AdventureWorks, 1, 0, 3) WITH TABLERESULTS'); GO SELECT * FROM #dbccpage

Vous pouvez, grce DBCC PAGE, observer la structure dallocation dans les fichiers de donnes. Dans len-tte renvoy par DBCC PAGE, vous trouvez lindication des pages de PFS, GAM et SGAM qui rfrencent la page. Au dbut de chaque intervalle GAM, vous trouvez une extension GAM qui ne contient que des pages de structure. La premire extension dun fichier est une extension GAM, qui contient en page 0 len-tte de fichier, en page 1 la premire page PFS, en page 2 la premire page GAM, etc. Mais, tout cela nous pouvons le dcouvrir en utilisant DBCC PAGE. Observons la premire page du fichier :
DBCC PAGE (AdventureWorks, 1, 0, 3)

Dans len-tte de la page, nous rcuprons les informations dallocation :


Allocation Status GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x44 ALLOCATED 100_PCT_FULL DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED
1. Voir nomenclature en dbut douvrage. Le drapeau de trace permet de modifier des options internes de SQL Server.

14

Chapitre 2. Architecture de SQL Server

Plus loin, nous pouvons observer les informations contenues dans len-tte. Par exemple :
FileGroupId = 1 FileIdProp = 1 Growth = 2048 . MinSize = 15360 Size = 30816 MaxSize = 65535

Status = 0

UserShrinkSize = 65535

Nous savons donc quelle est la premire page de PFS : 1:1. Un DBCC PAGE nous liste ensuite les pages quelle rfrence, avec pour chacune lindication de sa nature : IAM ou non. Voici un extrait du rsultat :
(1:651) (1:652) (1:653) (1:654) - (1:655) = = = = ALLOCATED ALLOCATED ALLOCATED ALLOCATED 0_PCT_FULL 0_PCT_FULL 0_PCT_FULL 0_PCT_FULL Mixed Ext IAM Page Mixed Ext Mixed Ext IAM Page Mixed Ext

La page 652 est donc une page IAM. Allons voir :


[] Metadata: AllocUnitId = 72057594052083712 Metadata: PartitionId = 72057594045726720 Metadata: IndexId = 1 Metadata: ObjectId = 402100473 m_prevPage = (0:0) m_nextPage = (0:0) pminlen = 90 m_slotCnt = 2 m_freeCnt = 6 m_freeData = 8182 m_reservedCnt = 0 m_lsn = (37:4181:117) [] IAM: Single Page Allocations @0x6105C08E Slot 0 = (1:653) Slot 3 = (0:0) Slot 6 = (0:0) Slot 1 = (0:0) Slot 4 = (0:0) Slot 7 = (0:0) Slot 2 = (0:0) Slot 5 = (0:0)

IAM: Extent Alloc Status Slot 1 @0x6105C0C2 (1:0) (1:8464) (1:8504) (1:8680) (1:8688) (1:8456) (1:8496) (1:8672) (1:30808) = NOT ALLOCATED = ALLOCATED = NOT ALLOCATED = ALLOCATED = NOT ALLOCATED

La page IAM contient un tableau dallocation de lextension, et pour chaque page de lextension, le dtail des pages alloues. Dans notre exemple, une seule page de lextension est alloue lobjet. On comprend donc quil sagit dune extension mixte. Plus loin, nous avons le dtail des pages et extensions alloues la table. Nous prendrons un exemple plus dtaill sur le sujet dans le chapitre traitant des index, afin de comprendre leur structure.

2.2 Structures de stockage

15

Sparse columns SQL Server 2008 introduit une mthode de stockage optimise pour les tables qui comportent beaucoup de colonnes pouvant contenir des valeurs NULL, permettant de limiter lespace occup par les marqueurs NULL. Pour cela, vous disposez du nouveau mot-cl SPARSE la cration de vos colonnes. Vous pouvez aussi crer une colonne virtuelle appele COLUMN_SET, qui permet de retourner rapidement les colonnes sparse non null. Cette voie ouverte la dnormalisation est manier avec prudence, et seulement si ncessaire.

2.2.2 Journal de transactions


Le journal de transactions (transaction log) est un mcanisme courant dans le monde des SGBD, qui permet de garantir la cohrence transactionnelle des oprations de modification de donnes, et la reprise aprs incident. Vous pouvez vous reporter la section 7.5 pour une description dtaille des transactions. En un mot, toute criture de donnes est enrle dans une transaction, soit de la dure de linstruction, soit englobant plusieurs instructions dans le cas dune transaction utilisateur dclare laide de la commande BEGIN TRANSACTION. Chaque transaction est pralablement enregistre dans le journal de transactions, afin de pouvoir tre annule ou valide en un seul bloc (proprit datomicit de la transaction) son terme, soit au moment o linstruction se termine, soit dans le cas dune transaction utilisateur, au moment dun ROLLBACK TRANSACTION ou dun COMMIT TRANSACTION. Toute transaction est ainsi compltement inscrite dans le journal avant mme dtre rpercute dans les fichiers de donnes. Le journal contient toutes les informations ncessaires pour annuler la transaction en cas de rollback (ce qui quivaut dfaire pas pas toutes les modifications), ou pour la rejouer en cas de relance du serveur (ce qui permet de respecter lexigence de durabilit de la transaction). Le mcanisme permettant de respecter toutes les contraintes transactionnelles est le suivant : lors dune modification de donnes (INSERT ou UPDATE), les nouvelles valeurs sont inscrites dans les pages du cache de donnes, cest--dire dans la RAM. Seule la page en cache est modifie, la page stocke sur le disque restant intacte. La page de cache est donc marque sale (dirty), pour indiquer quelle ne correspond plus la page originelle. Cette information est consultable laide de la colonne is_modified de la vue de gestion dynamique (DMV) sys.dm_os_buffer_descriptors :
SELECT * FROM sys.dm_os_buffer_descriptors WHERE is_modified = 1

La transaction valide est galement maintenue dans le journal. intervalle rgulier, un point de contrle (checkpoint) aussi appel point de reprise, est inscrit dans le journal, et les pages modifies par des transactions valides avant ce checkpoint sont crites sur le disque.

Les vues de gestion dynamique partir de la version 2005 de SQL Server, vous avez votre disposition, dans le schma sys, un certain nombre de vues systmes qui portent non pas sur les mtadonnes de la base en cours, mais sur ltat actuel du SGBD. Ces vues sont nommes vues de gestion dynamique (dynamic management views, DMV). Elles sont toutes prfixes par dm_. Vous

16

Chapitre 2. Architecture de SQL Server

les trouvez dans lexplorateur dobjets, sous vues/vues systme, et vous pouvez en obtenir la liste laide des vues de mtadonnes, par exemple ainsi : SELECT * FROM sys.system_objects WHERE name LIKE 'dm_%' ORDER BY name; Vous y trouverez la plupart des vues disponibles. Certaines ny figurent pas car elles sont non documentes, ou sont mises en uvre sous forme de fonctions. Les donnes renvoyes par ces vues dynamiques sont maintenues dans un cache spcial de la mmoire adresse par SQL Server. Elles ne sont pas stockes de faon persistante sur le disque : les compteurs sont remis zro lors du redmarrage du service SQL. Par consquent, pour que ces vues offrent des informations utiles, vous ne devez les consulter quaprs un temps significatif dutilisation du serveur. Nous utiliserons beaucoup les vues de gestion dynamiques tout au long de cet ouvrage.

Point de contrle et rcupration


Comment garantir la cohrence dune base de donnes en cas de panne ou de redmarrage intempestif ? Les techniques appliques dans SQL Server sont galement standards aux SGBDR. Premirement, le maintien de toutes les informations de transaction dans le journal, avec les marqueurs temporels de leur dbut et fin, assure quelles pourront tre reproduites. Lorsque linstance SQL redmarre, chaque base de donnes entre en tape de rcupration (recovery) : les transactions du journal non encore rpercutes dans les pages de donnes sont reproduites. Ce sont soit des transactions qui ntaient pas termines lors du dernier point de contrle (checkpoint), soit qui ont commenc aprs celui-ci. Si la transaction nest pas valide dans le journal (parce quelle a t annule explicitement, ou parce quelle na pas pu se terminer au moment de larrt de linstance), elle est annule. Selon le volume des transactions journalises et la date du dernier point de contrle, cette rcupration peut prendre du temps. SQL Server sassure donc dmettre un point de contrle suffisamment souvent pour maintenir une dure de rcupration raisonnable. Une option de linstance permet de rgler la dure maximum, en minutes, de la rcupration : le recovery interval. Par dfaut la valeur est 0, ce qui correspond une auto-configuration de SQL Server pour assurer dans la plupart des cas une rcupration de moins dune minute. Changer cette valeur diminue les frquences de point de contrle et augmente le temps de rcupration. Vous pouvez le faire dans les proprits de linstance dans SSMS, ou via sp_configure :
EXEC sp_configure 'show advanced option', '1'; RECONFIGURE; EXEC sp_configure 'recovery interval', '3'; RECONFIGURE WITH OVERRIDE; EXEC sp_configure 'show advanced option', '1';

La procdure stocke systme sp_configure est utilise pour modifier des options de configuration de linstance SQL Server. Certaines options sont aussi visibles dans les botes de dialogues de configuration dans SSMS. Par dfaut, sp_configure ne touche que les options courantes. Pour afficher et changer les options avan-

2.2 Structures de stockage

17

ces, vous devez utiliser sp_configure lui-mme pour les activer avec sp_configure 'show advanced option', '1'.

La frquence dexcution des points de contrle est donc calcule par rapport cet intervalle, et selon la quantit de transactions dans le journal. Elle sera basse pour une base principalement utilise pour la lecture, et de plus en plus haute selon le volume de modifications. Vous navez gnralement pas besoin de modifier cette option. Si vous faites lexprience de pointes dcriture sur le disque de donnes, avec une file dattente qui bloque dautres lectures/critures, et seulement dans ce cas, vous pouvez songer laugmenter, par exemple cinq minutes, en continuant surveiller les compteurs de disque, pour voir si la situation samliore. Nous reviendrons sur ces compteurs dans la section 5.3 concernant le moniteur systme. La conservation des transactions dans le journal aprs point de contrle est contrle par loption de base de donnes appele mode de rcupration (recovery model). Elle est modifiable par la fentre de proprits dune base de donnes dans SSMS, ou par la commande :
ALTER DATABASE [base] SET RECOVERY { FULL | BULK_LOGGED | SIMPLE }

Les trois options modifient le comportement du journal de la faon suivante : FULL Toutes les transactions sont conserves dans le journal, en vue de leur sauvegarde. Cette option nest donc activer que sur les bases de donnes pour lesquelles vous voulez pratiquer une stratgie de sauvegarde impliquant des sauvegardes de journaux. Seule cette option, allie aux sauvegardes, permet de raliser des restaurations un point dans le temps. Si le journal nest pas sauvegard, le fichier grandira indfiniment pour conserver toutes les transactions enregistres. BULK_LOGGED Afin de ne pas augmenter exagrment la taille du journal, les oprations en lot (BULK INSERT, SELECT INTO, CREATE INDEX) sont journalises de faon minimale, cest--dire que leurs transactions ne sont pas enregistres en dtail. Seules les adresses de pages modifies sont inscrites dans le journal. Lors de la sauvegarde de journal, les extensions modifies sont ajoutes la sauvegarde. Si une sauvegarde de journal contient des modifications en lot, la sauvegarde ne pourra pas tre utilise pour une restauration un point dans le temps. SIMPLE Dans le mode simple, le journal de transactions est vid de son contenu inutile (ce quon appelle la portion inactive du journal) aprs chaque point de contrle. Aucune sauvegarde de journal nest donc possible. Par contre, la taille du journal restera probablement raisonnable. En SQL Server, le journal de transactions consiste en un ou plusieurs fichiers, au remplissage squentiel, dans un format propritaire compltement diffrent des fichiers de donnes. Ce format nest pas publi par Microsoft, ce qui fait quil ny a pas rellement de mthode officielle pour en lire le contenu et en retirer des infor-

18

Chapitre 2. Architecture de SQL Server

mations exploitables. Il est galement impossible de rcuprer ou annuler une transaction valide partir du journal. Il est par contre possible de faire des sauvegardes de journal et de les restaurer un point dans le temps, annulant ainsi toutes les transactions valides cet instant. Le journal de transactions est donc une chane squentielle denregistrements de transactions, comportant toutes les informations ncessaires une rexcution de la transaction par le systme lors dune phase de restauration ou de rcupration, ainsi quun numro de squence denregistrement, le log sequence number, ou LSN. Une restauration de journal peut galement tre effectue jusqu un LSN. Encore faut-il savoir quel est le LSN dune opration recherche. Nous lavons dit, le format du journal nest pas connu. Il ny a que deux manires de lire ce journal de transactions : acheter un logiciel tiers, ou utiliser des instructions SQL Server non documentes.

Affichage du contenu du journal de transactions


Quelques diteurs, dont le plus connu est Lumigent avec son outil nomm Log Explorer , ont dchiffr, avec des mthodes dingnierie inverse (reverse engineering), le format du journal, et proposent des outils plus ou moins puissants pour voir et utiliser le contenu du journal et le dtail des oprations effectues par chaque transaction. SQL Server comporte galement quelques commandes non documentes cest-dire caches, et susceptibles dtre modifies ou retires sans pravis dans des versions suivantes du produit qui affichent des informations sur le journal. La plus complte est une fonction systme, sys.fn_dblog (ou ::fn_dblog), que vous pouvez appeler ainsi dans le contexte de la base de donnes dsire :
SELECT * FROM sys.fn_dblog(DB_ID(),NULL)

Cette vue vous retourne les informations de LSN, le type dopration effectue, lidentifiant de transaction, et plusieurs lments intressants comme le nom de lunit dallocation (la table ou lindex affect), le nombre de verrous poss, etc. Il ne manque que le dtail des modifications, qui pourrait servir savoir prcisment ce qui sest produit. Chaque ligne comporte une colonne nomme Previous LSN , qui permet de suivre lordre des instructions dune mme transaction. Le type dinstruction est assez clair, comme par exemple LOP_BEGIN_XACT, LOP_MODIFY_ROW et LOP_COMMIT_XACT. Cette fonction peut tre utile pour obtenir le dtail transactionnel dune opration. Prenons un exemple. Nous chercherons dabord obtenir un journal aussi vide que possible. Nous mettons donc la base de donnes en mode simple, et nous provoquons manuellement un CHECKPOINT.
ALTER DATABASE AdventureWorks SET RECOVERY SIMPLE; GO CHECKPOINT;

2.2 Structures de stockage

19

SQL Server 2008 En SQL Server 2005, nous pouvons aussi lancer la commande BACKUP LOG AdventureWorks WITH TRUNCATE_ONLY; pour vider manuellement le journal. Cette commande nest plus prise en charge dans SQL Server 2008.

En excutant la fonction fn_dblog(), nous constatons que le journal est pratiquement vide. Effectuons une modification simple dans la table Sales.Currency, qui contient les rfrences de devises :
UPDATE Sales.Currency SET Name = Name WHERE CurrencyCode = 'ALL'

La modification demande ne ralise aucun changement. SQL Server va-t-il tout de mme faire un UPDATE ? Grce fn_dblog() nous constatons que non. Une seule opration est journalise, de type LOP_SET_BITS, une mise jour des informations dune page, pas de son contenu. Essayons maintenant de rellement changer quelque chose :
UPDATE Sales.Currency SET Name = 'Lek2' WHERE CurrencyCode = 'ALL'

Les oprations du tableau 2.1 sont inscrites dans le journal.


Tableau 2.1 Oprations inscrites dans le journal Operation LOP_BEGIN_XACT LOP_MODIFY_ROW LOP_SET_BITS LOP_DELETE_ROWS LOP_SET_BITS LOP_INSERT_ROWS LOP_COMMIT_XACT Context LCX_NULL LCX_CLUSTERED LCX_DIFF_MAP LCX_MARK_AS_GHOST LCX_PFS LCX_INDEX_LEAF LCX_NULL NULL Sales.Currency.PK_ Currency_CurrencyCode Unknown Alloc Unit Sales.Currency.AK_ Currency_Name Sales.Currency.AK_ Currency_Name Sales.Currency.AK_ Currency_Name NULL AllocUnitName

Ici les oprations sont claires : modification de la ligne (LOP_MODIFY_ROW sur lindex clustered, donc sur la table), et ensuite opration de suppression puis dinsertion dans lindex AK_Currency_Name, qui contient la colonne modifie dans sa cl. Vous verrez parfois lUPDATE journalis non comme un LOP_MODIFY_ROW, mais comme un couple de LOP_DELETE_ROWS et LOP_INSERT_ROWS, car SQL Server gre deux

20

Chapitre 2. Architecture de SQL Server

types de mises jour : lupdate direct et lupdate diffr (deferred), selon les cas. Lupdate diffr correspond donc une suppression de ligne suivie dun ajout de ligne. Cest par exemple le cas lorsque la mise jour augmente la taille de la ligne et la force tre dplace dans une autre page de donnes.

2.2.3 Taille des fichiers


Physiquement, le journal est compos de fichiers virtuels de journal (virtual log files, ou VLF), qui sont des blocs logiques dans le mme fichier de journal, de taille et de nombre variables. SQL Server essaie de conserver un nombre limit de VLF, car ceux-ci psent sur les performances. Lorsque le journal augmente de taille automatiquement, le nombre de VLF augmente et est plus important que si la taille avait t prvue correctement ds le dpart. Ne soyez donc pas trop avare despace la cration de vos bases de donnes. Vous pouvez maintenir plusieurs fichiers de journal sur la mme base de donnes. Cela ne constituera pas une optimisation de performances par paralllisation des critures, car, nous lavons vu, le journal de transactions est par nature squentiel. Chaque fichier sera utilis tour de rle, SQL Server passant au fichier suivant lorsque le prcdent sera plein. Cela ne sera donc utile que pour pallier un manque despace sur des disques durs de petite taille.

2.3 SQLOS
Si vous voulez obtenir les meilleures performances de votre serveur SQL, la chose la plus importante en ce qui concerne le systme lui-mme, est de le ddier SQL Server. La couche reprsente par le systme dexploitation lui-mme, et les autres applications sexcutant en concurrence, doit tre rduite au maximum, en dsactivant par exemple les services inutiles. SQL Server est livr avec dautres services, notamment toute la partie Business Intelligence, qui fait appel des moteurs diffrents : Analysis Service est un produit conceptuellement et physiquement tout fait spar du moteur relationnel, de mme que Reporting Services utilise des technologies qui entrent en concurrence avec SQL Server (dans la version 2005, Reporting Services est fond sur IIS, le serveur web de Microsoft, qui ne fait notoirement pas bon mnage avec SQL Server, en terme de performances sentend. Dans SQL Server 2008, il est plus intgr et utilise notamment SQLOS). Il est essentiel de dplacer ces services sur des serveurs diffrents de ceux que vous voulez ddier aux performances de SQL Server, cest--dire de la partie relationnelle, dont le travail de base est de stocker et de service de linformation avec la vlocit et la cohrence maximales. Un serveur est une ville trop petite pour deux personnalits comme SQL Server. Bien que SQL Server soit troitement li au systme dexploitation Windows, il ne se repose pas, comme la quasi-totalit des applications utilisateurs, sur toutes ses

2.3 SQLOS

21

fonctionnalits daccs et de partage des ressources systme. Les recherches en bases de donnes, un domaine actif depuis longtemps, montrent que les meilleures performances sont atteintes en laissant le soin au SGBDR de grer certaines parties de ce qui est traditionnellement de la responsabilit du systme dexploitation. Oracle, par exemple, cherche depuis longtemps minimiser le rle de lOS, en contournant les systmes de fichiers journaliss laide de raw devices (Oracle directIO), en proposant un outil comme ASM (Automatic Storage Manager) pour grer directement le systme de fichiers, ou en intgrant depuis peu sa propre couche de virtualisation, base sur Xen. SQL Server nchappe pas cette rgle. Il intgre sa couche de gestion de fonctionnalits bas niveau, nomme SQLOS (pour SQL Server Operating System). Elle prend en charge des fonctionnalits dordonnancement des threads de travail, la gestion de la mmoire, la surveillance des ressources, les entres/sorties, la synchronisation des threads, la dtection des verrous mortels (deadlocks), etc. qui permettent de grer lintrieur mme de SQL Server, proche des besoins du moteur, les lments essentiels un travail efficace avec les ressources de la machine. Vous trouvez un schma des diffrents modules prsents dans SQLOS en figure 2.3.

Figure 2.3 Modules de SQLOS

Vous pouvez observer lactivit de SQLOS laide de vues de gestion dynamique prfixes par sys.dm_os_. Les vues de gestion dynamique (dynamic management views, DMV) sont des vues qui retournent des informations sur ltat du systme, et non sur des donnes ou des mtadonnes stockes dans une base. SQLOS permet de grer efficacement les systmes multiprocesseurs. Un ordonnanceur (scheduler) est li chaque processeur physique, qui gre lintrieur de SQL Server, les threads, en mode utilisateur (user mode) (le systme dexploitation sous-jacent gre les threads en mode noyau (kernel mode)). La raison en est que SQL Server connat mieux ses besoins de multitche que le systme dexploitation. Windows gre les threads de faon dite premptive, cest--dire quil force les processus sarrter pour laisser du temps aux processus concurrents. Les ordonnanceurs de SQL Server fonctionnent en mode non premptif : lintrieur de SQLOS, les processus

22

Chapitre 2. Architecture de SQL Server

laissent eux-mmes la place aux autres, dans un mode collaboratif, ce qui permet dobtenir de meilleures performances. Un ordonnanceur est li un processeur physique. Il gre plusieurs threads, qui sont eux-mmes lis des workers. Le worker est le processus de travail. Il excute un travail compltement, ce qui vite les changements de contextes qui peuvent se produire en cas de multitche premptif. Un changement de contexte (context switch) reprsente la ncessit, pour un processus, de sauver son tat lorsquil donne la main, puis de le recharger lorsquil reprend le travail, comme un travailleur doit dposer ses affaires dans son placard en partant, et les reprendre en revenant. Certains threads lintrieur de lespace de SQL Server ne sont pas lancs par SQL Server, cest le cas lors de lexcution de procdures stockes tendues. Nous voyons tout cela schmatiquement sur la figure 2.4, avec les vues de gestions dynamique qui correspondent.

Figure 2.4 SQLOS, lordonnanceur

Ces vues vous permettent de voir les processus en activit, la mmoire de travail utilise par chacun, sils sont en travail ou en attente, etc. Outre la gestion locale des processus, SQLOS assure aussi la gestion locale de lallocation mmoire. Un Memory Broker calcule dynamiquement les quantits optimales de RAM pour les diffrents objets en mmoires. Ces objets sont des caches (buffers), des pools, qui grent des objets plus simples que les caches, par exemple la mmoire pour les verrous, et des clerks, qui sont des gestionnaires despace mmoire fournissant des services dallocation et de notification dtat de la mmoire. Ces diffrents lments sont reproduits sur la figure 2.5.

2.3 SQLOS

23

Figure 2.5 SQLOS, la mmoire

Des vues de gestion dynamique donnent des informations sur les caches : sys.dm_exec_cached_plans ; sys.dm_os_buffer_descriptors ; sys.dm_os_memory_cache_clock_hands ; sys.dm_os_memory_cache_counters ; sys.dm_os_memory_cache_entries ; sys.dm_os_memory_cache_hash_tables.

Nous reverrons certaines de ces vues dans notre chapitre sur le choix du matriel. Regardez ces vues, lorsque vous avec une comprhension gnrale de SQL Server : elles vous sont utiles pour dtecter des problmes ventuels, comme de la pression mmoire, ou des contentions de CPU.

SQL Server 2008 ajoute quelques vues de gestions dynamiques sur SQLOS : sys.dm_os_nodes donne des informations sur les nuds SQLOS, cest--dire les liens fondamentaux entre SQLOS et Windows. sys.dm_os_process_memory donne le rsum de lutilisation mmoire de linstance SQL. Trs utile pour obtenir les valeurs globales, en Ko. sys.dm_os_sys_memory donne le rsum de lutilisation mmoire de Windows sur le systme.

3
Optimisation du matriel

Objectif
Tout comme une plante ne peut atteindre sa taille normale et porter ses fruits pleine maturit que lorsque la terre et les conditions mtorologiques sont adaptes, SQL Server est totalement dpendant de la qualit du matriel sur lequel il est install. Ce chapitre prsente les lments importants de ce matriel.

3.1 CHOIX DE LARCHITECTURE MATRIELLE


SQL Server est par nature grand consommateur de ressources physiques. Un systme de gestion de bases de donnes a besoin de bonnes performances des sous-systmes de stockage, avec lesquels il travaille beaucoup, non seulement pour crire et lire des donnes parfois en gros volumes, mais aussi pour maintenir les journaux de transaction, et tempdb, la base de donnes qui recueille les tables temporaires, les tables de travail internes ainsi que les versions de lignes dans les fonctionnalits de row versioning. La mmoire vive est galement sollicite. SQL Server doit conserver un certain nombre de choses dans lespace dadressage virtuel (le virtual address space, ou VAS) du systme dexploitation. Dans le VAS, SQL Server pose la mmoire des sessions utilisateurs, des verrous, du cache de plans dexcution, la mmoire dexcution des requtes et le cache de donnes. Lexcution des requtes peut tre trs consommatrice de RAM : le moteur dexcution doit passer dun oprateur de plan dexcution lautre les lignes rcupres, et parfois tout un ensemble de lignes pour les traiter en une seule fois (pour raliser un tri ou une table de hachage, par exemple). Ces oprations sont galement consommatrices de processeur. Tous les lments sont donc prendre en considration. Dans un systme traitant un volume raisonnable de

26

Chapitre 3. Optimisation du matriel

donnes et suffisamment pourvu en RAM, la vitesse du sous-systme disque nest pas le critre le plus dcisif, car la plupart des donnes lues le seront depuis le cache de donnes, en RAM. Dans tous les cas, plus la quantit de RAM est importante, meilleures seront les performances. En ce qui concerne les CPU, un systme multiprocesseurs est aujourdhui incontournable. SQL Server utilise intensivement le multi-processing, en rpartissant ses requtes sur des threads de travail, ou en paralllisant la mme requte si lactivit de la machine est suffisamment lgre pour le permettre au moment de lexcution.

3.1.1 Processeur(s)
Cest une vidence, SQL Server utilise activement le multi-processing. Nhsitez pas btir une machine de huit processeurs ou plus. Ldition standard ne supporte que quatre CPU. Entendez quatre processeurs physiques (quatre sockets). Si vous utilisez des processeurs multi-core (dual ou quad), cela augmente le nombre de processeurs supports par cette dition. De mme, le mode de licence par processeur de SQL Server est entendre par socket. Pour quatre quad-core, donc seize processeurs logiques, vous vous acquittez dune licence de quatre processeurs. Le multi-core est bien pris en charge, mais, lhyper-threading peut poser des problmes : nous le dtaillerons dans la section suivante.

Paralllisme
Les oprations de bases de donnes, notamment la gnration du plan dexcution et la restitution de donnes, sont habituellement gourmandes en temps processeur. Les serveurs sont aujourdhui de plus en plus multiprocesseurs, et les processeurs (notamment Intel) modernes intgrent des architectures de multi-threading ou de multi-core, qui leur permettent de travailler en parallle. Deux cas de figure sont possibles : lexcution en parallle de requtes diffrentes ou lexcution en parallle de diffrentes parties de la mme requte. Cest ce deuxime cas de figure quon appelle la paralllisation dune requte. La dcision de parallliser une requte est prise en temps rel par le moteur relationnel, selon ltat actuel du systme : si lactivit est faible, et la requte est estime coteuse (longue), SQL Server sera plus enclin parallliser que si la requte est simple et que le systme est charg de demandes de petites requtes. En dautres termes, la paralllisation est plus intressante sur un systme analytique de type OLAP, que sur une base de donnes transactionnelle trs active, de type site web. Le degr de paralllisme, ainsi que le seuil partir duquel la dure estime dune requte la classe comme candidate la paralllisation, sont des paramtres du systme que vous pouvez modifier :
EXEC sp_configure 'show advanced options', 1 RECONFIGURE GO EXEC sp_configure 'affinity mask' EXEC sp_configure 'max degree of parallelism' EXEC sp_configure 'cost threshold for parallelism'

3.1 Choix de larchitecture matrielle

27

Loption affinity mask vous permet de nattribuer SQL Server que certains processeurs. La valeur par dfaut, 0, indique que tous les CPU sont utiliss. Pour spcifier seulement certains processeurs, attribuez un entier correspondant au masque de bits reprsentant, du bit de poids faible au bit de poids fort, le ou les processeurs utiliser. Par exemple, pour nutiliser que les processeurs 0, 2 et 4, attribuez la valeur binaire 00010101, donc 21 en dcimal. Ce masque daffinit ne peut donc grer que 32 processeurs, ce qui est de toute faon la limite de Windows Server 2003 32 bits. Sur les architectures 64 bits, une option supplmentaire est disponible : affinity64 mask pour laffinit sur 32 processeurs supplmentaires. moins que vous nayez librer des processeurs pour une autre application (par exemple une autre instance de SQL Server), il vaut mieux laisser SQL Server utiliser tous les processeurs disposition. Dans le cas dun systme virtualis, que nous verrons plus loin, ce sera de toute manire la machine virtuelle de grer les processeurs physiques quelle utilise, donc ceci ne concerne pas cette section. Loption max degree of parallelism indique, sur le nombre de processeurs dfini dans laffinit, combien pourront tre enrls dans la paralllisation dune mme requte. La valeur par dfaut, 0, indique que tous les CPU peuvent tre utiliss. Cette option modifie le comportement de SQL Server pour lexcution de toutes les requtes. Exemple :
EXEC sp_configure 'max degree of parallelism', 2

Pour permettre une paralllisation sur deux processeurs au maximum. Lorsquune requte est paralllise, elle doit utiliser le nombre de processeurs indiqu dans max degree of parallelism , pas moins. En dautres termes, si vous laissez SQL Server parallliser sans limite sur un systme comportant beaucoup de processeurs, tous les processeurs devront prendre en charge la requte, et le cot affrant la synchronisation entre les processeurs sera srement plus important que le gain de performance de lexcution en parallle, faisant donc plus de mal que de bien. De plus, le paralllisme peut poser problme sur des processeurs utilisant la technologie dhyper-threading. Cette technologie propritaire dIntel, intgre aux processeurs Pentium 4, est moins utilise aujourdhui mais pourrait faire son retour dans la future architecture Nehalem . Elle permet sous certaines conditions damliorer les performances dapplications multi-threades. Un processeur hyper-thread est vu par Windows comme deux processeurs1, et SQL Server peut tenter de parallliser une requte sur ce qui reprsente en ralit le mme processeur physique. On obtiendra ainsi un ralentissement au lieu dune amlioration, en provoquant des attentes inter-threads. Ces ralentissements pourront se constater grce un type particulier dattente (wait). Nous reviendrons en dtail sur la dtection des attentes dans le chapitre 7. Sachez pour linstant que vous pouvez obtenir des statistiques dattentes
1. SQL Server utilise le membre de structure SYSTEM_INFO.dwNumberofProcessors de lAPI Windows pour connatre le nombre de processeurs. Cette proprit retourne le nombre total de processeurs physiques et logiques

28

Chapitre 3. Optimisation du matriel

de processus SQL Server grce la vue de gestion dynamique sys.dm_os_wait_stats. La colonne wait_type indique pour quelle raison lattente a eu lieu. Lors dune excution en parallle, les threads ont besoin de schanger des donnes. Le type CXPACKET indique une attente de synchronisation dchange de paquets. Ainsi, si vous constatez des ralentissements lis des attentes de type CXPACKET, il ne vous reste plus qu jouer avec max degree of parallelism .

Si vous constatez, sur des processeurs hyper-threads, une augmentation de lutilisation du CPU mais une baisse de performances, lies des messages visibles dans le journal derreur de SQL Server (errorlog) indiquant quun thread na pas russi acqurir un spinlock (une structure lgre de synchronisation de threads), vous vous retrouvez certainement dans la situation dcrite par Slava Oks ici : http://blogs.msdn.com/slavao/archive/2005/11/12/492119.aspx. La solution est de dsactiver purement et simplement lhyper-threading dans le BIOS de votre machine.

Comme remarque gnrale, considrant les problmes potentiels que gnre lhyper-threading, faites des tests de charge hyper-threading dsactiv puis activ, et si vous ne voyez pas de gain de performance notable avec lhyper-threading, dsactivezle pour de bon.

NUMA Comme nous allons le voir, sur une architecture NUMA, des bus spars sont allous aux processeurs pour accder la mmoire. La mmoire accde sur le bus du processeur est nomme mmoire locale, et la mmoire accessible sur les autres bus est logiquement nomme mmoire distante. Vous vous doutez que laccs la mmoire distante est plus long et coteux que laccs la mmoire locale. Il faut donc viter de parallliser une requte sur des processeurs qui accdent des bus diffrents. Vous le faites en limitant le max degree of parallelism au nombre de processeurs sur un nud.

Les bonnes pratiques sont donc les suivantes : sur des systmes trs sollicits en requtes courtes, dsactivez totalement la paralllisation (EXEC sp_configure 'max degree of parallelism', 1) : cela vite que quelques requtes de reporting mobilisent plusieurs processeurs qui auraient t utiles pour dautres lots en attente ; sur des systmes comportant plus de huit processeurs, limitez max degree of parallelism 8. Une paralllisation sur plus de 8 processeurs devient excessivement coteuse en synchronisation ; sur une architecture NUMA, max degree of parallelism ne doit pas tre suprieur au nombre de processeurs sur un nud NUMA. Il existe une option de requte, MAXDOP, qui produit le mme effet de faon localise. Par exemple :

3.1 Choix de larchitecture matrielle

29

SELECT c.LastName, c.FirstName, e.Title FROM HumanResources.Employee AS e JOIN Person.Contact AS c ON e.ContactID = c.ContactID OPTION (MAXDOP 2);

Comme elle lemporte sur loption de linstance, cette option de requte peut tre utile pour augmenter le nombre de processeurs dfini par max degree of parallelism , pour une requte spcifique.

Pression sur les processeurs


videmment, la pression sur les processeurs est montre par leur niveau dutilisation, dans le moniteur de performances ou dans le gestionnaire de tches de Windows (task manager).

Process Explorer Nous vous conseillons cet utilitaire dvelopp par Mark Russinovich fait partie de la panoplie des outils winternals disponibles maintenant sur le site de Microsoft. Il vous offre une vision beaucoup plus complte que le gestionnaire de tches. http://technet.microsoft.com/en-us/sysinternals/.

Mais une forte utilisation des processeurs ne veut pas dire pression (car ils peuvent travailler beaucoup sans tre stresss), et encore moins que le problme vient des processeurs eux-mmes. La pression est montre par le compteur de performances System:Processor queue length, qui indique la file dattente sur les processeurs. Les compteurs de performances sont visibles dans le moniteur systme, dtaill dans la section 5.3 Les vues de gestion dynamique sont aussi de bons indicateurs :
SELECT SUM(signal_wait_time_ms) as signal_wait_time_ms, CAST(100.0 * SUM(signal_wait_time_ms) / SUM (wait_time_ms) AS NUMERIC(20,2)) as [%signal (cpu) waits], SUM(wait_time_ms - signal_wait_time_ms) as resource_wait_time_ms, CAST(100.0 * SUM(wait_time_ms - signal_wait_time_ms) / SUM (wait_time_ms) AS NUMERIC(20,2)) as [%resource waits] FROM sys.dm_os_wait_stats

Cela vous donne la raison des attentes dans SQL Server, soit sur le processeur (%signal (cpu) waits), soit sur les ressources (%resource waits), comme le disque ou les verrous. Un pourcentage important dattente processeur (en dessus de 20 %) indique une pression sur les CPU. Pour connatre les dtails par type dattente :
SELECT wait_type, (wait_time_ms * .001) as wait_time_seconds FROM sys.dm_os_wait_stats GROUP BY wait_type, wait_time_ms ORDER BY wait_time_ms DESC;

Vous pouvez aussi voir le nombre de tches gres simultanment par un ordonnanceur :

30

Chapitre 3. Optimisation du matriel

SELECT scheduler_id, current_tasks_count, runnable_tasks_count FROM sys.dm_os_schedulers WHERE scheduler_id < 255;

Ce qui vous donne une ide prcise de la charge de travail que doit supporter le systme. Un nombre lev de runnable_tasks_count, par exemple 10 sur plusieurs processeurs, peut vous indiquer quune mise jour matrielle (ou une simplification de votre code) est ncessaire. Voici quelques ressources utiles : Troubleshooting Performance Problems in SQL Server 2005 http://www.microsoft.com/technet/prodtechnol/sql/2005/tsprfprb.mspx measure CPU pressure http://blogs.msdn.com/sqlcat/archive/2005/09/05/461199.aspx

3.1.2 Mmoire vive


Utilisation de toute la mmoire en 32 bits
Dans un environnement 32 bits, lespace dadressage est limit 4 Go. Un processeur 32 bits utilise 32 bits pour positionner chaque octet de mmoire. Chaque bit reprsentant deux valeurs possibles (O ou 1), une adresse mmoire longue de 32 bits peut donc reprsenter 2 puissance 32 positions. 2^32 / 1 024 / 1 024 = 4 096 Mo 4 Go. Cet espace dadressage est divis en deux parties gales. Les deux premiers gigaoctets sont disponibles aux applications, ce quon appelle la mmoire utilisateur (user memory), les deux suivants sont rservs au systme dexploitation, et constituent la mmoire du noyau (kernel memory). Mme si votre serveur na pas 4 Go de RAM physique, Windows adresse toute la plage en effectuant un mapping dadresses virtuelles vers les adresses physiques, laide dun processus appel VMM, Virtual Memory Manager.1 Le VMM est comme un escroc immobilier qui vend des clients diffrents la mme parcelle de terre. Grce lui, chaque processus en activit voit un espace de 2 Go de RAM. Sur une machine ddie SQL Server, lactivit du systme dexploitation luimme sera peu importante, et elle ne ncessitera pas les 2 Go de kernel memory. Vous avez la possibilit dajuster la distribution entre la mmoire utilisateur et la mmoire du systme dexploitation, laide dune option de chargement du noyau. Dans le fichier boot.ini, situ la racine de votre partition de boot, sur la ligne correspondant votre systme dexploitation dans la section [operating systems], ajoutez loption /3GB. Exemple :
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" / fastdetect /3GB

Ce faisant, vous indiquez Windows de librer 1 Go supplmentaire pour les applications qui le prennent en charge (elles doivent avoir activ le bit
1. William BOSWELL, Inside Windows Server 2003, Addison Wesley, 2003.

3.1 Choix de larchitecture matrielle

31

IMAGE_FILE_LARGE_ADDRESS_AWARE dans leur en-tte, ce qui est bien sr le cas de SQL Server), et de ddier 1 Go la mmoire du noyau. Cest loption que nous vous recommandons sur une machine ddie, si vous avez jusqu 16 Go de mmoire physique. Au-del, cette option est viter. En effet, dans ce cas Windows a besoin de mmoire supplmentaire pour des tables de page (page table entries, ou PTE) destines adresser cette mmoire. Si vous laissez loption /3GB sur une machine qui possde plus de 16 Go de RAM installs, la mmoire supplmentaire sera simplement ignore, ce qui nest sans doute pas le but recherch. Vous pouvez savoir si /3GB est activ depuis SQL Server, en interrogeant la vue sys.dm_os_sys_info, colonne virtual_memory_in_bytes :
SELECT CASE WHEN virtual_memory_in_bytes / 1024 / (2048*1024) < 1 THEN 'Pas activ' ELSE '/3GB' END FROM sys.dm_os_sys_info;

Bien, mais que faire de la mmoire au-del de lespace dadressage virtuel des 4 Go ? Depuis le Pentium Pro, tous les processeurs Intel 32 bits (famille x86) incluent un mode de mapping de mmoire appel PAE (Physical Address Extension), qui permet daccder 64 Go. En mode PAE, lunit de gestion mmoire (MMU, Memory Management Unit, un circuit intgr responsable de laccs la mmoire et des fonctionnalits de pagination) se voit ajouter des lignes dadressage supplmentaires qui lui permettent de grer des adresses sur 36 bits. 2^36 / 1 024 / 1 024 = 65 536 Mo = 64 Go. Pour activer ce mode PAE sur Windows ( partir de Windows 2000), vous devez ajouter loption /PAE dans le mme fichier boot.ini :
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" / fastdetect /PAE

Cette option active le chargement dune image particulire du noyau de Windows (Ntkrnlpa.exe, ou Ntkrpamp.exe sur une machine multiprocesseurs, au lieu de Ntoskrnl.exe). Ceci permet donc au systme dexploitation daugmenter sa limite adressable 64 Go, mais les applications restent limites par leur propre capacit dadressage 32 bits. Windows leur montre des espaces de mmoire virtuelle sur 4 Go, diffrents les uns des autres, quelles ne peuvent dpasser. Lutilisation de PAE seule est donc intressante lorsque plusieurs processus demandent de la mmoire, mais dans notre cas, nous voulons faire profiter le seul processus sqlservr.exe de toute notre mmoire. Nous devons alors utiliser AWE. AWE (Address Windowing Extensions) est un API Windows qui permet aux applications qui limplmentent daccder plus de mmoire physique que leur espace dadressage virtuel 32 bits. Comme son nom lindique, AWE effectue un fentrage en rgions despaces dadressage, et rserve un plus grand nombre de rgions de

32

Chapitre 3. Optimisation du matriel

mmoire physique de mme taille. Le processus de fentrage permet ensuite dattribuer un espace une rgion de mmoire, en balayage : toute la mmoire est accessible, mais pas en mme temps. AWE est intressant pour les applications qui manipulent de larges volumes de donnes comme un SGBDR. Sagissant dune API, il faut donc que le code de lapplicatif lutilise. Vous devez activer son utilisation dans SQL Server, laide dune option de configuration du systme.
sp_configure 'show advanced options', 1 RECONFIGURE sp_configure 'awe enabled', 1 RECONFIGURE

La mmoire alloue par AWE est verrouille en RAM et ne sera jamais change (swappe) sur le disque. Elle ne pourra pas non plus tre rclame par le systme dexploitation et est donc idale pour grer du cache. SQL Server lutilise uniquement pour le buffer. En dautres termes, en 32 bits, la mmoire au-del de la limite des 4 Go ne servira quau cache de pages de donnes. Tous les autres modules (la mmoire de travail, les sessions, les verrous, le cache de plans dexcution) devront se placer dans lespace dadressage virtuel. Cest pour cette raison quil est important de choisir loption /3GB quand les conditions le permettent. Notez que AWE est indpendant de PAE : il peut donc placer une partie du buffer aussi dans lespace dadressage virtuel.

Prise en charge du 64 bits


Depuis la version 2000, SQL Server est livr en deux compilations pour les architectures de processeurs 32 bits (x86) et 64 bits (ia64 maintenant appel Itanium, x64, et amd64). Le choix dune architecture 64 bits est de plus en plus intressant, notamment en rapport cot / performances, et vous devriez aller dans ce sens si vous avez des besoins mme moyennement importants. Lavantage, outre une capacit de calcul plus importante des processeurs eux-mmes, est la quantit de mmoire adressable par un pointeur 64 bits (donc 8 octets). Par dfaut, lespace dadressage virtuel pour les applications (user memory) est de 8 To (tera-octets), ou 7 To sur les processeurs Itanium, ce qui semble suffisant pour les quelques prochaines annes. Lavantage ici est net pour SQL Server, et notamment pour la mmoire dexcution et le cache de plans, qui ne sont plus limits aux 2 ou 3 premiers Go comme en 32 bits.

Dcisionnel Le 64 bits est spcialement intressant pour les services qui tournent autour de SQL Server, comme Analysis Services ou Reporting Services. Ceux-ci nutilisent pas AWE et ne peuvent profiter de la mmoire supplmentaire en 32 bits. Ils restent tous en dessous des 4 Go.

Bien entendu, loption /3GB na pas de sens en 64 bits. En revanche, et cela peut paratre tonnant, AWE peut se rvler intressant, pour la raison que nous avons dj voque : comme il verrouille les pages en mmoire vive, elles ne peuvent tre

3.1 Choix de larchitecture matrielle

33

changes sur le disque. AWE est donc pris en charge en 64 bits, et vous pouvez lactiver pour viter le swap.

Clients vitez dinstaller des versions 32 bits sur une architecture 64 bits, et surtout les outils clients comme SSMS. Les applications 32 bits tournent dans une couche dabstraction nomme WOW (Windows On Windows), ce qui peut diminuer leurs performances. Si vous voulez installer les outils clients sur votre serveur, installez les versions 64 bits. Ny installez pas BIDS (Business Intelligence Development Studio), qui ne possde pas de version 64 bits.

AWE permet aussi parfois un accs plus rapide la mmoire. Lorsque le systme dexploitation manipule la mmoire, il accde aux entres de la table de pages (PTE) qui pointent sur les portions de mmoire physique laide dun numro appel le PFN (page frame number) si cette portion est en mmoire. Lorsquelle est swappe sur le disque, la PTE ne contient plus de PFN. Mais le PFN contient certaines donnes utiles au systme, et celui-ci en extrait rgulirement des informations. Chaque accs verrouille la PTE afin dviter quun autre processus ne la modifie. Windows essaie de rendre ce verrouillage aussi lger que possible (le service pack 1 de Windows Server 2003 a notamment apport des amliorations de ce point de vue), mais il continue exister. La gestion des pages utilisant AWE est simplifie. Les PTE sont verrouilles leur acquisition, et le verrou est maintenu pour les protger contre toute modification daffectation, ou swap sur le disque. Cela amliore donc les performances des oprations daffectation de mmoire, et acclre la phase dacquisition de mmoire (rampup), particulirement sur une architecture NUMA : nous allons voir pourquoi.

NUMA
NUMA est lacronyme de Non-uniform Memory Access, une architecture matrielle spcifique destine optimiser laccs la mmoire des systmes comportant un nombre important de processeurs. Aujourdhui, un processeur fonctionne beaucoup plus rapidement que la mmoire vive, et passe donc une partie de son temps attendre la fin des oprations daccs celle-ci. Pour viter ces attentes, les processeurs intgrent de la mmoire cache. Mais il ne sagit que dun pis-aller. Lattente est potentiellement plus importante sur un systme comportant beaucoup de processeurs en architecture SMP (Symmetric Multi-Processing), car tous les processeurs accdent la mmoire travers le mme bus partag. NUMA offre une architecture o la mmoire est spare par processeurs, place sur des bus de donnes diffrents, et est donc accessible en parallle. Pour rpondre aux cas o plusieurs processeurs demandent les mmes donnes, NUMA intgre des solutions matrielles ou logicielles pour changer les donnes entre les diffrentes mmoires, ce qui peut bien sr ralentir les processeurs sollicits. Les gains de performance dun systme NUMA sont donc variables, dpendant en gnral du nombre de processeurs : NUMA devient intressant partir de 8 12 processeurs, o larchitecture SMP commence devenir problmatique. Pour obtenir un gain de cette architecture, les applications doivent reconnatre NUMA

34

Chapitre 3. Optimisation du matriel

(tre NUMA-aware) pour configurer lexcution de leur threads par nuds NUMA. Les applications qui ne reconnaissent pas NUMA peuvent tre ralenties, cause des diffrences de temps pour accder des parties de mmoires situes sur des bus diffrents. Pourquoi AWE est-il intressant avec NUMA ? Comme le PFN (stock dans la PTE, comme nous lavons vu) montre quel nud NUMA appartient la page de mmoire, la lecture de cette information ( laide de lAPI QueryWorkingSetEx depuis le Service Pack 1 de Windows Server 2003) ncessite une attribution et une libration de verrou. De plus, cette lecture doit tre ralise priodiquement, afin de prendre en compte les changements dattribution physique de la page, qui peuvent rsulter dune pagination sur le disque. AWE verrouille les pages en mmoire, ce qui garantit donc quelles ne seront jamais pagines. Ainsi, une application NUMAaware na besoin de rcuprer lattribution physique dune page quune seule fois, sur de la mmoire AWE.

Soft-NUMA Pour tester limplmentation de leur moteur sous NUMA, les quipes de dveloppement de SQL Server 2005 ont bti une version logicielle de larchitecture NUMA, qui en simule larchitecture matrielle. Ils se sont aperus que cette implmentation logicielle amliorait les performances sur les systmes multiprocesseurs SMP, et mme NUMA. Cet outil fait la base pour le test, est donc pass en production sous le nom de soft-NUMA. Si vous avez au moins 8 processeurs, essayez de configurer soft-NUMA pour SQL Server, et comparez les performances. Pour ce faire, vous crez des affinits de processeurs par nuds soft-NUMA dans la base de registre, dans la cl HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\90\NodeConfiguration\. Pour les informations pratiques, reportez-vous aux BOL, section How to : Configure SQL Server to Use Soft-NUMA

Pression sur la mmoire


SQL Server ragit la pression sur la mmoire, cest--dire au manque de mmoire lorsque celle-ci est rclame par des processus. Elle peut librer des espaces mmoire qui taient utiliss, par exemple par le cache de donnes ou le cache de procdures. En 32 bits, le cache de donnes peut rsider dans la mmoire gre par AWE, mais tout le reste doit tre dans lespace dadressage virtuel, en dessous de la limite des 4 Go. Vous pouvez dterminer sil manque de la mmoire pour le cache de donnes en surveillant les compteurs de performances de lobjet SQL:Buffer Manager : Buffer cache hit ratio Pourcentage de pages qui ont pu tre servies du cache, sans tre cherches sur le disque. Page life expectancy Nombre de secondes durant lesquelles une page non utilise reste dans le cache. Stolen pages Nombre de pages voles du cache pour tre utilises ailleurs. Nous dtaillons ces compteurs dans la section 5.3.1. Buffer cache hit ratio doit tre lev (plus de 97 %). Plus Page life expectancy est lev, mieux cest :

3.1 Choix de larchitecture matrielle

35

SQL Server a assez de mmoire pour conserver ses pages dans le cache. Plus le nombre de pages voles est faible et mieux cest. Pour observer les caches, vous disposez des objets de compteur SQL:Cache, SQL:Memory, SQL:Memory Manager et SQL:Plan Cache. Si vous voyez des fluctuations ( la baisse) du nombre dobjets dans le cache, vous avez affaire une pression mmoire (sauf si vous avez excut des commandes qui vident le cache de plans, comme un attachement de base de donnes, ou une commande DBCC). Vous pouvez aussi utiliser les vues de gestion dynamique pour surveiller la mmoire. Cette requte vous donne la taille des diffrents caches :
SELECT Name, Type, single_pages_kb, single_pages_kb / 1024 AS Single_Pages_MB, entries_count FROM sys.dm_os_memory_cache_counters ORDER BY single_pages_kb DESC

Nous vous renvoyons aux ressources suivantes pour entrer dans les dtails : Troubleshooting Performance Problems in SQL Server 2005 : http://www.microsoft.com/technet/prodtechnol/sql/2005/tsprfprb.mspx Vous trouverez des informations utiles dans cette entre de blog : http:// blogs.msdn.com/slavao/archive/2005/02/19/376714.aspx

3.1.3 Utilisation de la mmoire vive par SQL Server


Les administrateurs stonnent souvent de la manire dont SQL Server occupe la mmoire vive. Certains semblent sinquiter de laugmentation progressive et sans retour de loccupation en RAM du processus sqlservr.exe, et craignent une fuite de mmoire et une consommation exagre de ressources. Il sagit au contraire dun comportement souhaitable. Il faut comprendre que SQL Server est conu pour tre seul, sans concurrence dautres applicatifs, sur un serveur. Ainsi, par dfaut (vous pouvez limiter la mmoire disponible, nous le verrons plus loin), il sapproprie toutes les ressources raisonnablement disponibles. Si vous lisez ce livre, vous cherchez sans doute assurer un fonctionnement optimal de SQL Server. Vous ne pouvez le faire rellement quen lui ddiant une machine, et en lui permettant de sattribuer la RAM dont il a besoin. Dans une configuration par dfaut, une instance SQL Server ne se limite pas en RAM. Au dmarrage, elle calcule la quantit de mmoire rserve pour ses diffrents lments, et sattribue ensuite physiquement (elle valide ) la mmoire rserve selon ses besoins. Comme le comportement diffre selon les utilisations de la mmoire, nous allons dtailler le comportement des lments importants pour les performances. SQL Server utilise la RAM pour diffrents caches, dont les plus importants concernent les pages de donnes, et le plan dexcution des requtes.

36

Chapitre 3. Optimisation du matriel

Buffer
Le cache de donnes (appel le buffer, ou buffer pool) maintient en mmoire vive autant quil est possible de pages de donnes et dindex. La lecture partir de la RAM tant environ mille fois plus rapide que sur un disque dur, on en comprend aisment lintrt. Ce cache est gr par un composant de SQLOS nomm le buffer manager, dont le travail est la lecture des pages du disque vers le buffer, et lcriture des pages modifies du buffer vers le disque. Lors du dmarrage de linstance, SQL Server calcule et rserve lespace dadressage virtuel du buffer (la cible de mmoire, ou memory target). Il sagit despace rserv, mais la mmoire physique ne sera prise que selon les besoins. La mmoire rellement utilise est donc infrieure lespace rserv tant que les donnes demandes par les requtes ne lauront pas rempli (cette priode avant remplissage est appele le ramp-up). Vous pouvez voir la mmoire cible et la mmoire rellement utilise (en pages de 8 Ko) dans les colonnes bpool_commit_target et bpool_committed de la vue de gestion dynamique sys.dm_os_sys_info :
SELECT bpool_commit_target, bpool_committed FROM sys.dm_os_sys_info;

Une commande DBCC permet de vider les pages du buffer : DBCC DROPCLEANBUFFERS. Comme son nom lindique, seules les pages propres (non modifies) sont supprimes, les autres attendent un point de contrle pour tre crites sur le disque. Vous pouvez donc vous assurer de vider le plus de pages possibles en lanant dabord la commande CHECKPOINT :
CHECKPOINT; DBCC DROPCLEANBUFFERS;

Lorsque SQL Server a valid de la mmoire physique pour le buffer, il ne la rend plus au systme. Depuis le moniteur systme ou le gestionnaire de tche (colonnes Memory Usage et Virtual Memory Size), vous verrez le processus sqlserver.exe prendre de plus en plus de mmoire virtuelle au fur et mesure de lexcution des requtes, et ne jamais la rendre. Faisons-en la dmonstration de lintrieur de SQL Server.
/* nous crons premirement des instructions de SELECT pour chaque table d'AdventureWorks */ SELECT 'SELECT * FROM [' + TABLE_CATALOG + '].[' + TABLE_SCHEMA + '].[' + TABLE_NAME + '];' FROM AdventureWorks.INFORMATION_SCHEMA.TABLES; /* pages valides du buffer */ SELECT bpool_committed, bpool_commit_target FROM sys.dm_os_sys_info; -- nous avons copi ici le rsultat de la gnration de code SELECT * FROM [AdventureWorks].[Production].[ProductProductPhoto]; /* [] */ SELECT * FROM [AdventureWorks].[Production].[ProductPhoto]; GO -- pages valides du buffer

3.1 Choix de larchitecture matrielle

37

SELECT bpool_committed, bpool_commit_target FROM sys.dm_os_sys_info; GO -- vidons le buffer CHECKPOINT; DBCC DROPCLEANBUFFERS; GO -- pages valides du buffer SELECT bpool_committed, bpool_commit_target FROM sys.dm_os_sys_info; GO

Les rsultats (sur notre portable) sont prsents sur le tableau 3.1.
Tableau 3.1 Rsultats des pages valides du buffer bpool_committed avant toute requte : aprs les SELECT : aprs avoir vid le buffer : 3776 0 0 bpool_commit_target 25600 0 0

SQL Server conserve donc la mmoire valide. Mais les pages sont-elles bien effaces du buffer ? Nous pouvons le vrifier laide de la DMV sys.dm_os_buffer_descriptors qui dtaille chaque page du buffer :
SELECT page_type, count(*) as page_count, SUM(row_count) as row_count FROM sys.dm_os_buffer_descriptors WHERE database_id = DB_ID('AdventureWorks') GROUP BY page_type ORDER BY page_type;

Aprs lexcution de la commande DBCC DROPCLEANBUFFERS, cette requte donne bien un rsultat vide. La DMV que nous venons dutiliser a un intrt purement informatif. Il nest pas crucial pour lamlioration des performances de dtailler le contenu du buffer. SQL Server effectue un trs bon travail par lui-mme et na pas besoin dinspection. Mais la curiosit peut parfois vous amener consulter le buffer. Voici une requte qui vous permet de dtailler son contenu par tables :
USE AdventureWorks; GO SELECT object_name(p.object_id) AS ObjectName, bd.page_type, count(*) as page_count, SUM(row_count) as row_count, SUM(CAST(bd.is_modified as int)) as modified_pages_count FROM sys.dm_os_buffer_descriptors bd JOIN sys.Allocation_units a ON bd.allocation_unit_id = a.allocation_unit_id

38

Chapitre 3. Optimisation du matriel

JOIN

sys.partitions p ON p.partition_id = a.container_id WHERE bd.database_id = DB_ID('AdventureWorks') AND object_name(p.object_id) NOT LIKE 'sys%' GROUP BY object_name(p.object_id), bd.page_type ORDER BY ObjectName, page_type;

max server memory


Vous pourriez vous interroger : SQL Server ne va-t-il pas saccorder trop de mmoire, au dtriment du bon fonctionnement du systme dexploitation ? videmment non, la prise de mmoire est pense au niveau de lOS. Par dfaut, SQL Server acquiert autant de mmoire possible sans mettre en danger le systme. Il utilise pour ce faire lAPI de notification mmoire (Memory Notification API) de Windows. En ralit, SQL Server peut rendre au systme la mmoire physique utilis par le buffer dans un cas : lorsque, fonctionnant sur une plate-forme 32 bits de version au moins quivalente Windows XP ou Windows 2003, et lorsque AWE est activ, SQL Server permet une allocation dynamique (dynamic memory allocation) du buffer. Linstance continue acqurir de la mmoire physique jusqu ce que la limite indique dans loption de serveur max server memory1 soit atteinte, ou que Windows signale quil na plus de mmoire disponible pour les applications. Lorsque Windows signale un manque de mmoire vive, SQL Server restitue de la mmoire (si les conditions vues prcdemment sont remplies) jusqu atteindre la limite infrieure indique dans loption min server memory. Cette dernire option ne signifie pas que, au dmarrage, SQL Server va immdiatement occuper cette quantit de mmoire. Dans tous les cas, SQL Server acquiert sa mmoire graduellement. Elle donne simplement une limite en dessous de laquelle il ny aura pas de restitution de mmoire lOS.

Faut-il fixer la mmoire ? Un conseil de performance remontant aux prcdentes versions de SQL Server tait de fixer la mmoire, cest--dire de donner une valeur identique min server memory et max server memory. SQL Server 2005 et 2008 grent dynamiquement la mmoire dans SQLOS de faon optimale : il peut tre contre performant de fixer la mmoire, et cest inutile.

Dans le cas dune allocation dynamique de mmoire, SQL Server ajuste sa consommation selon les disponibilits de la RAM. Autrement dit, si dautres processus acquirent et librent de la mmoire, SQL Server en rend et en reprend en miroir. SQL Server peut ainsi sajuster au rythme de plusieurs Mo par seconde.

1. Ces options sont activables avec sp_configure ou graphiquement dans SSMS.

3.1 Choix de larchitecture matrielle

39

SQL Server 2005, dition Entreprise Le ramp-up est optimis pour tre plus rapide sur les machines ayant beaucoup de mmoire, parce que les requtes de pages uniques sont converties en requtes de 8 pages contigus. En 2008 cette fonctionnalit nest plus limite ldition entreprise.

3.1.4 Disques
Le choix des sous-systmes disque est bien sr important. Il est probable que vous soyez de plus en plus dots de SAN, o les performances sont gres de faon moins directe que pour des disques locaux, chaque vendeur fournit son outil de gestion.
Vous trouvez des conseils pour les SAN dans cette entre de blog en trois parties : http://blogs.msdn.com/sqlcat/archive/2005/10/11/479887.aspx.

Pour le reste, les rgles sont simples : les disques doivent tre rapides et profiter dun taux de transfert lev. Cest moins important pour les disques qui hbergeront les fichiers de donnes, si la taille des bases est infrieure la mmoire vive. ce moment, la plupart des requtes seront satisfaites par le cache de donnes (le buffer). Autant que possible, choisissez du RAID 10 (ou 01) plutt que du RAID 5, car les performances en criture du RAID 5 sont nettement plus faibles. Dplacez autant que possible vos journaux de transactions sur des disques ddis, ce qui permettra une criture squentielle optimale. Ddiez le disque le plus rapide, de prfrence un disque local, tempdb. Tout ce qui est dans tempdb est jetable : vous navez donc pas besoin dassurer une scurit importante de ce disque. Un simple miroir pour viter les interruptions de service suffit. Vous avez quelques outils disposition pour mesure la performance de vos disques : IOMeter et SQLIO sont des outils de pur contrle des performances (benchmarking) de disque en ligne de commande. Ils sont libres et tlchargeables sur Sourceforge pour le premier, et sur le site de Microsoft pour le second. Ils vous donnent les performances de vos disques. Vous trouvez des conseils dutilisation en consultant des entres de blog et rfrences : http://blogs.msdn.com/sqlcat/archive/2005/11/17/493944.aspx http://sqlblog.com/blogs/linchi_shea/archive/2007/02/21/parse-the-sqlio-exeoutput.aspx http://www.microsoft.com/whdc/device/storage/subsys_perf.mspx SQLIOSim est un outil de pur stress sur le disque, qui se fonde sur le mme code daccs au disque que SQL Server (qui na pas besoin dtre install), pour mesurer la robustesse de votre stockage de masse. Plus dinformation dans lentre de base de connaissance Microsoft 231619 : http://support.microsoft.com/kb/231619

Pression sur le disque


Le compteur de performances (voir section 5.3) surveiller pour identifier une contention sur les disques est PhysicalDisk: Avg. Disk Queue Length. Il indique la taille de la file dattente, cest--dire les processus qui attendent la libration du

40

Chapitre 3. Optimisation du matriel

disque dj utilis par un autre processus, pour pouvoir lire ou crire. Si cette valeur est rgulirement en dessus de 2 pour un certain temps, vous avez un problme. PhysicalDisk: Avg. Disk Sec/Read et PhysicalDisk:Avg. Disk Sec/Write sont les temps moyens en secondes ncessaires une lecture ou une criture. On considre quune moyenne de moins de 10 millisecondes est trs bonne, et qu partir de 20 millisecondes cela indique une lenteur. Ds 50 millisecondes, il y a une srieuse contention. Physical Disk: %Disk Time est le pourcentage de temps o le disque est en activit. Ce nombre devrait rester en dessous de 50. Ces nombres sont ajuster sur des disques RAID.
Le calcul dajustement est disponible dans le document Troubleshooting Performance Problems in SQL Server 2005 : http://www.microsoft.com/technet/prodtechnol/sql/2005/tsprfprb.mspx.

La requte suivante procure les demandes dentres/sorties en attente, par fichier de bases de donnes :
SELECT vf.database_id, vf.file_id, vf.io_stall, pio.io_pending_ms_ticks FROM sys.dm_io_virtual_file_stats(NULL, NULL) vf JOIN sys.dm_io_pending_io_requests pio ON vf.file_handle = pio.io_handle

io_pending_ms_ticks est le temps dattente en millisecondes. Vous pouvez aussi obtenir des statistiques dattente totales par fichier :
SELECT UPPER(LEFT(mf.physical_name, 2)) as disk, DB_NAME(vf.database_id) as db, mf.name as file_name, vf.io_stall, vf.io_stall_read_ms, vf.io_stall_write_ms FROM sys.dm_io_virtual_file_stats(NULL, NULL) vf JOIN sys.master_files mf ON vf.database_id = mf.database_id AND vf.file_id = mf.file_id ORDER BY disk, db, file_name

3.1.5 Virtualisation
La virtualisation est une technique en plein essor, dont lobjectif est de sparer la couche logicielle de la couche matrielle : un environnement complet, cest--dire le systme dexploitation et les applications quil hberge, sexcute dans une machine virtuelle. Les ressources physiques que cette machine virtuelle utilise sont contrles par le logiciel de virtualisation.

3.1 Choix de larchitecture matrielle

41

Cette architecture permet de dplacer ou dupliquer un environnement, ou de monter en charge trs rapidement, sans rinstallation et sans aucune modification de la configuration logicielle. Elle permet galement de rationaliser le nombre de machines physiques, en multipliant leur capacit daccueil. Cest un paradigme qui regroupe les notions de monte en charge verticale et horizontale (scale up et scale out) en une seule. La virtualisation permet de mettre en place trs rapidement des environnements de test ou de dveloppement, mais elle est de plus en plus utilise pour hberger de vrais serveurs de production, lorsquune bonne performance doit tre assure. Pour ce type de besoin, le logiciel de virtualisation doit sappuyer sur une architecture matrielle adapte. Parmi les quelques outils logiciels de virtualisation sur le march, les deux plus rpandus sont Microsoft Virtual Server et VMware Server (puis XenEnterprise et VirtualIron). Ils sont utiles pour les environnements de test et de dveloppement. Pour obtenir les performances ncessaires une machine de production, une solution plus puissante comme VMware ESX est requise. Son nom complet est VMware ESX Server, car il sagit en ralit dun systme dexploitation en tant que tel. Alors que les logiciels traditionnels de virtualisation sinstallent sur un systme dexploitation tel que Windows ou un UNIX, ESX regroupe les deux couches : OS et virtualisation, ce qui permet un niveau de performance largement suprieur. Il comporte galement des outils dadministration qui facilitent la gestion centralise des machines virtuelles, leur clonage et leur migration, notamment. Lallocation des ressources peut tre ralise dynamiquement. Un module comme VMware DRS (Distributed Resource Scheduler) permet dattribuer les ressources entre les machines virtuelles selon leur besoin, par rapport des rgles prdfinies. Lusage de machines virtuelles facilite galement la sauvegarde des donnes : une machine virtuelle peut tre sauvegarde directement au niveau du systme hte (ESX lui-mme dans ce cas), et un systme de basculement automatique peut tre configur laide de loutil VMware HA (High Availability). Une machine virtuelle peut tre automatiquement relance sur un autre serveur faisant partie de la ferme . Nous nallons pas dtailler loptimisation de serveurs virtualiss, cest un travail en soi1. Nous numrons simplement quelques rgles de base suivre si vous voulez mettre en place ce type dinfrastructure. Vous trouvez sur le site de VMware un outil de benchmarking nomm Vmmark. Choisissez des processeurs Dual-Core (ou mieux, Quad-Core). Des tests effectus par Dell montrent un gain de performance denviron 50 % sur des Intel Xeon Dual-Core 32 bits par rapport des processeurs single-core, et dautres
1. Si vous vous y intressez, nous vous conseillons louvrage de Edward Haletky, VMware ESX Server in the Enterprise, que vous trouverez dans la bibliographie.

42

Chapitre 3. Optimisation du matriel

tests sur des Xeon Dual-Core 64 bits vont jusqu un gain de performance de 81 % par rapport des Xeon single-core. Une machine virtuelle peut tre supervise exactement comme un serveur physique, les outils et mthodes que nous prsentons dans le chapitre 5 sont donc tout fait valables pour les machines virtuelles, la diffrence prs quune machine virtuelle peut montrer des signes vitaux tout fait normaux, mais des performances diminues, parce que dautres machines virtuelles sexcutant sur le mme matriel consomment trop de ressources. Processeurs, mmoire, rseau et disques peuvent tre partitionns pour permettre leur utilisation par plusieurs machines virtuelles. Il est noter qu lheure actuelle Microsoft ne supporte officiellement quun seul produit de virtualisation pour un dploiement de SQL Server en cluster : le sien, Virtual Server.
Vous pouvez consulter larticle de base de connaissance 897615 : Support policy for Microsoft software running in non-Microsoft hardware virtualization software (http:// support.microsoft.com/kb/897615) pour vous tenir inform de lvolution de cette politique.

3.2 CONFIGURATION DU SERVEUR


Microsoft essaie de faire en sorte que SQL Server sautoconfigure autant que possible. Un certain nombre de rgles et dalgorithmes sont implments pour lui permettre de sajuster aux conditions du systme hte. La configuration interviendra donc souvent lorsque cest ncessaire, aprs un test de charge, plutt que de faon systmatique linstallation. Dans des cas o le besoin se fait sentir doptimiser au maximum, dans les moindres dtails, les performances du serveur, quelques possibilits de configuration fine existent : En utilisant loption -x au lancement de SQL Server, vous pouvez dsactiver le maintien par linstance des statistiques dutilisation des CPU et de taux dutilisation du cache. Ces statistiques ne sont pas particulirement coteuses, mais cest une option dans les cas o toute optimisation est bonne prendre. Vous pouvez ajouter cette option laide du Configuration Manager, dans la configuration du service SQL, onglet Advanced, option Startup Parameters. Lors de chaque opration de lecture de donnes, SQL Server vrifie lintgrit des pages lues. Par dfaut, cette vrification se fait laide dun calcul de somme de contrle (checksum) sur le contenu de la page, qui est ensuite compar la somme de contrle stocke dans len-tte de la page lcriture des donnes. Cette opration est galement lgre et utile pour dventuelles (et fort rares) corruptions de donnes. Vous pouvez nanmoins la dsactiver,

3.2 Configuration du serveur

43

ou la remplacer par la mthode historique de vrification dintgrit physique, moins complte mais plus lgre, nomme torn page detection (un bit est pos lcriture de chaque bloc de 512 octets, pour indiquer lcriture correctement effectue de ce bloc). Pour une base de donnes existante, utilisez linstruction ALTER DATABASE :
-- pour vrifier le type de vrification de page : SELECT name, page_verify_option_desc FROM sys.databases; -- pour dsactiver la vrification par CHECKSUM : ALTER DATABASE [AdventureWorks] SET PAGE_VERIFY NONE; -- pour remplacer par une vrification par TORN PAGE DETECTION : ALTER DATABASE [AdventureWorks] SET PAGE_VERIFY TORN_PAGE_DETECTION;

4
Optimisation des objets et de la structure de la base de donnes

Objectif
La toute premire tape de loptimisation est la conception ou design, la dfinition de larchitecture logique de vos bases de donnes. La qualit du modle de donnes influencera tout le reste, et sera un atout ou un handicap pour toute la vie de la base. La modlisation est dabord logique, faite dentits et de relations, puis elle est physique, avec limportance du choix des types de donnes pour chaque colonne, puisque SQL est un langage fortement typ. Enfin, une tape plus physique encore est de prparer soigneusement les structures du serveur : bases de donnes et tables.

4.1 MODLISATION DE LA BASE DE DONNES


La plupart des progiciels maintiennent ou srialisent, des informations. Les outils de bureautique traitent videmment des documents divers contenus dans des fichiers, les applications serveurs doivent souvent communiquer avec des systmes htrognes et schangent des informations sous des formes standardises : protocoles SMTP, HTTP, SOAP La faon dont chaque diteur implmente le stockage local de ces informations lui reste toutefois propre. Dans ce monde de concurrence logi-

46

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

cielle, les SGBDR occupent une place particulire. Leur charpente, ainsi que bon nombre de dtails de leur implmentation font lobjet dun consensus, bti soit sur le rsultat de recherches (modle et algbre relationnels, indexation), soit sur des normes tablies (comme la norme SQL). Bien entendu, la manire dont les octets qui composent les donnes sont organiss sur le disque dpend de chaque diteur, mais leur structure logique, leur organisation en tables et relations ce quon appelle le modle relationnel repose sur une base commune. Ce modle sest impos durant ces vingt dernires annes la suite des recherches dEdgar F. Codd, qui cherchait rendre les bases de donnes indpendantes dune implmentation ou dun langage particulier. Cest donc une spcificit des systmes de gestion de bases de donnes de vous laisser organiser la structure des informations. La plupart des autres types de progiciels stockent leurs donnes de manire fige, parce que leurs besoins sont simplement diffrents. Cette libert qui vous est laisse est un des lments qui fait toute la puissance dun SGBDR, mais non sans consquences : dune bonne structuration des donnes dpendent les performances de tout le systme. Nous pouvons mme dire que loptimisation dune base de donnes repose en premier lieu sur la qualit de son modle de donnes. Il est illusoire dattendre des miracles de requtes SQL excutes sur des donnes mal organises. Pour cette raison, nous allons passer du temps sur ce sujet.

4.1.1 Terminologie du modle relationnel


La modlisation se base sur la cration pralable dun modle conceptuel de donnes (MCD), une reprsentation de la structure dentits et de relations indpendante de toute implmentation physique. Le rsultat pourra ensuite tre appliqu nimporte quel SGBDR, moyennant une adaptation ses particularits dimplmentation, travers un modle physique de donnes (MPD). La terminologie utilise dans le MCD diffre lgrement de celle utilise dans les implmentations physiques. Pour mmoire, en voici un rsum dans le tableau 4.1.
Tableau 4.1 Terminologie du modle relationnel MCD Type Attribut ou proprit Entit ou relation Association Tuple Type Colonne Table Relation Ligne MPD Dfinition Le type de donnes dun attribut. Donne lmentaire lintrieur dune entit. Concept, objet, concert ou abstrait, qui reprsente un lment identifiable du systme modliser. Un lien entre deux entits. Une instance dentit = une ligne dans une table.

4.1 Modlisation de la base de donnes

47

MCD Identifiant Cardinalit Cl Cardinalit MPD Dfinition Attribut ou ensemble dattributs qui permet didentifier uniquement chaque tuple dune entit. Dcrit la nature dune association en indiquant, chaque extrmit, le nombre doccurrences de tuples possible.

La premire tape de modlisation, ltape conceptuelle, gagne tre simple et concrte. Votre modle doit pouser la ralit. Une bonne faon de sy prendre est de reproduire dans un schma les phrases simples dcrivant les tats et les activits modliser. Les noms (sujet et complment) seront repris en entits, et le verbe sera lassociation.

Cardinalit
La cardinalit reprsente le nombre doccurrence, de tuples, de lignes. Dans un modle, elle dcrit le nombre doccurrences possible de chaque ct dune association. Par exemple, une association entre un contact et ses numros de tlphone est en gnral une cardinalit de un--plusieurs, car on peut attribuer plusieurs numros au mme contact. Mais cela pourrait tre : Un--un Un seul numro de tlphone est permis par contact. Un--plusieurs Nous lavons dit, plusieurs numros par contact. Plusieurs--plusieurs Pour des numros professionnels communs, au bureau, qui pourraient servir joindre plusieurs personnes. Les entits qui possdent des cardinalits plusieurs se voient souvent dotes de discriminants, cest--dire des attributs qui identifient le tuple parmi les autres. Par exemple, ce serait ici un type de numro de tlphone : fixe, mobile, ou priv, professionnel. Ces discriminants pourront ensuite, dans limplmentation physique, faire partie dune cl unique si ncessaire.

Choix des cls


Une cl dentit est un attribut, ou un ensemble dattribut, qui permet didentifier un tuple unique. Cest un concept essentiel de la modlisation, comme de limplmentation physique relationnelle. Une association un-- ne peut se concevoir que si la partie un de lassociation est garantie unique. Si cette rgle est viole, cest toute la cohrence du modle qui est remise en question. De mme, de nombreuses rgles mtiers impliquent lunicit. Il ne peut y avoir quun seul conducteur de camion au mme moment, un chque doit porter un numro unique et ne peut tre encaiss que sur un seul compte bancaire. Un numro ISBN ne peut tre attribu qu un seul livre encore disponible, etc. Dans un modle, plusieurs types de cls sont possibles : Primaire La cl primaire est, parmi les cls candidates, celle qui est choisie pour reprsenter la cl qui garantit aux yeux du modle, lunicit dans lentit.

48

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Dans sa reprsentation physique dans SQL Server, chaque ligne doit contenir une valeur, elle ne pourra donc pas accepter de marqueur NULL. Elle sera le plus souvent celle qui sera choisie comme cl trangre, dans les associations. Candidate La cl candidate pourrait avoir t la cl primaire. Elle constitue une autre faon de garantir un tuple unique. Elle na pas t choisie comme cl primaire soit par malchance pour elle, soit pour des raisons bassement physiques (sa taille par exemple, qui influe sur la taille des cls trangres et potentiellement de lindex clustered nous verrons ce concept dans le chapitre 6). Naturelle Par opposition une cl technique, une cl naturelle est cre sur un attribut signifiant, qui existe dans le monde rel modlis par le schma. Par exemple, un numro ISBN, un EAN13, un numro national didentit, un numro de tlphone, etc. Le danger de la cl naturelle est son manque potentiel de stabilit, le fait que son format peut changer la suite dune dcision que vous ne matrisez pas, et la possibilit quelle soit rattribue travers le temps. Lavantage est que la cl elle-mme est dj signifiante. Technique (surrogate) Une cl technique est une cl en gnral numrique, cre par dfaut dexistence dune cl naturelle, pour unifier les tuples. Elle est souvent implmente physiquement avec un procd de calcul dauto-incrment, comme un squenceur ou la proprit IDENTITY en SQL Server. Elle a souvent tendance devenir naturelle pour les utilisateurs des donnes, qui parlent alors du client 4567 ou de la facture 3453. Si vous souhaitez vous prvenir de cette drive , il est bon de la cacher autant que possible dans les interfaces utilisateur. Intelligente Une cl intelligente est une cl qui contient plusieurs informations. Elle permet donc dextraire des contenus utiles. Par exemple, un numro ISBN contient le numro de zone linguistique, et le numro dditeur. Le Numro national didentit (numro de scurit sociale) donne le sexe ainsi que lanne, le mois, le dpartement et la commune de naissance. Elle peut permettre de gagner du temps dans les recherches, cette logique pouvant tre extraite de la cl par programmation, sans recherche dans des tables de rfrence (quil faut crer pour contrle, mais quil ne sera pas toujours utile de requter dans les extractions). Une cl intelligente peut tre gnre par le systme avec des informations utiles. Par exemple, une cl destine unifier un systme rparti peut contenir un identifiant de base, de serveur ou de data center. trangre Une cl trangre est une cl dplace dans une table fille, correspondant une cl de la table mre.

Modlisation des relations


Les relations sont exprimes par des cardinalits, cest--dire le nombre possible de tuples de chaque ct de la relation. On en distingue plusieurs types : Cardinalit identifiante La cl primaire dune table est migre dans la cl primaire dune autre table : relation de un--un.

4.1 Modlisation de la base de donnes

49

Cardinalit non identifiante La cl primaire est migre dans un attribut non-cl de la table fille : relation de un--plusieurs. Cardinalit optionnelle Relation non identifiante avec valeur non ncessaire : relation de zro--plusieurs. Cardinalit rcursive Lattribut cl est li un autre attribut de la mme table. Cardinalit plusieurs--plusieurs Non-identifiant dans les deux sens. En implmentation physique, cette cardinalit ncessite une table intermdiaire. Lorsquune cl est migre plusieurs fois dans la mme table fille, on parle de rles jous par la cl. Il est de bon aloi de prfixer le nom de lattribut dplac par son rle. Par exemple, pour un lien avec un identifiant de personne : VendeurPersonneID, et ClientPersonneID.

4.1.2 Normalisation
Un bon modle de donnes nexprime une information quune seule fois, au bon endroit. Ds que la mme information est place deux endroits du modle, sa valeur est compromise parce quelle peut tre diffrente. Si nous pouvons trouver le numro de tlphone dun contact dans la table Contact et dans la table Adresse qui lui est lie, que faire lorsque les deux numros ne sont pas les mmes (et croyeznous, tt ou tard il y aura des diffrences) ? Il y a peut-tre un numro erron, mais lequel ? Ou il sagit simplement de son numro priv et de son numro professionnel mais lequel ? Toute information doit tre prcise, dlimite et unifie : tel est le but de la normalisation. Ce qui sous-tend le concept de normalisation, est en fin de compte, le monde rel. Il ny a rien de particulirement technique, il ny a pas dingnierie particulirement obscure et sophistiques dans la modlisation de donnes, fort heureusement. La plupart des tentatives de sophistication, surtout si elles entranent le modle sloigner de la ralit, sont des errements malheureux. Que cela signifie-t-il ? Quun modle de donnes relationnel est form dentits et de relations : des tables, et des relations entre ces tables. Chaque entit dcrit une ralit dlimite, observable dans le champ modlis par la base de donnes. Il suffit dobserver autour de soi, de comprendre la nature lmentaire des processus luvre, pour dessiner un modle de donnes. Avons-nous des clients, des magasins, des employs, des produits ? Nous aurons donc les tables Client, Magasin, Employe, Produit. Un employ vend-il ses clients des produits dans un magasin ? Nous aurons donc une table de vente, qui rfrence un vendeur, un magasin, un client, un produit, une date donne, avec un identifiant de vente, peut-tre un numro de ticket de caisse. Mais, vend-il plusieurs produits lors de la mme vente ? Nous aurons donc supprimer lidentifiant de produit dans la table de ventes, et crer une table de produits vendus, lie la vente. Lemploy est-il toujours dans le mme magasin ? Sans doute, alors retirons lidentifiant de magasin de la vente, et ajoutons lidentifiant du magasin dans la table des employs. Et si lemploy peut changer de magasin ? Crons alors une table interm-

50

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

diaire qui lie lemploy au magasin, avec cest une bonne ide des dates de validit. Vous trouverez sur la figure 4.1 un modle trs simple de ces entits.

Figure 4.1 Premier modle Commerce

Rgles mtier Puisque nous abordons lide de dates de validit, vous devez tre extrmement attentif lors de la phase de modlisation aux rgles mtier (business rules). Elles doivent tre places autant que possible dans la structure. Les rgles complexes pourront tre implmentes en dclencheurs (triggers).

En rsum, il faut stocker autant dinformations utiles possibles, pour permettre de retrouver des donnes qui ont du sens et de la valeur, et, par consquent, placer linformation au bon endroit, cest--dire dans la bonne entit, celle laquelle elle appartient naturellement, en respectant un principe important : lconomie. Cela veut dire, viter la duplication de donnes, qui alourdit le modle, conceptuellement et physiquement, et diminue les performances. La granularit des donnes est aussi prendre en compte : dsirez-vous des donnes dtailles (tout un ticket de caisse, produit par produit), ou seulement des totaux ? En gnral, en application OLTP, les dtails sont conservs : vous en aurez forcment besoin un jour. Dans une application OLAP, on ne garde parfois que des agrgats, par exemple des totaux par jour.

4.1 Modlisation de la base de donnes

51

Formes normales
Normaliser un modle de donnes veut dire le rendre conforme des rgles nommes formes normales . Ce quon appelle les formes normales sont des lois importantes de structuration du modle, qui permettent de sassurer que celui-ci vite les piges les plus lmentaires, en assurant une base de conception favorable un stockage utile et optimal des donnes. Par utile nous entendons favorable lextraction des donnes dsires, dans la plupart des cas de figures. Pour tre complet, les formes normales principales sont au nombre de six. Toutefois, les trois premires sont les plus importantes et les plus utiles dans la vie relle de la modlisation de donnes. Chaque forme normale complte les formes prcdentes. Il est donc ncessaire de les appliquer dans lordre.

Premire forme normale (1FN) Tout attribut doit contenir une valeur atomique, cest--dire que toutes ses proprits doivent tre lmentaires. En clair, cela signifie que vous utiliserez une colonne par pice dinformation stocker, et quen aucun cas vous ne vous permettrez de colonnes concatnant des lments diffrents dinformation. Cette rgle est importante pour deux raisons : elle va vous permettre toute la souplesse ncessaire la restitution et la manipulation des donnes, et elle va vous permettre, bien plus en aval, de vous assurer de bonnes performances. Crer le modle dune base de donnes est aussi un exercice de prudence et danticipation. Ne prenez pas de dcision la lgre. Si vous estimez par exemple quune ligne dadresse peut tre contenue dans une seule colonne ('12, boulevard de Codd', par exemple), tesvous sr que vous naurez jamais besoin donc que personne ne vous demandera jamais dun tri des adresses par rue et numro de rue ? Si vous modlisez la base de donnes dun service de gestion des eaux, ne voudrez-vous pas fournir aux employs chargs de relever les compteurs une liste des habitants, dans lordre du numro dimmeuble de la rue ? Comment ferez-vous sans avoir isol ce numro dans un attribut (une colonne) ddi ? Comme beaucoup, vous allez vous chiner rcuprer ce numro avec des fonctions telles que SUBSTRING(), en prenant en compte toutes les particularits de saisie possibles. Vous navez alors plus de garantie de rsultats corrects, seulement celle dune baisse de performances. En rsum, toute valeur dont vous savez, ou souponnez, que vous allez devoir la manipuler, lafficher, la chercher individuellement devra faire lobjet dun attribut, donc tre stocke dans sa propre colonne. Ne crez des colonnes contenant des types complexes que lorsque leur relation est indissociable.
Attributs de rserve Une autre rflexion : ne crez pas dattributs inutiles. Crer des colonnes rserves un usage ultrieur, par exemple, na aucun sens. Vous alourdissez le modle et la taille de vos tables pour rien. Les versions modernes des SGBDR comme SQL Server permettent trs facilement dajouter ou de supprimer des colonnes aprs cration de la table. Mme lorsque les objets font parties dune rplication, il est devenu ais, partir de SQL Server 2005, de rpliquer aussi les changements de structure. Il ny a donc aucune justification rationnelle crer des colonnes rserves.

52

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Deuxime forme normale (2FN) Lentit doit respecter la 1FN. Tous les attributs doivent tre un fait concernant la cl tout entire, et non pas un sous-ensemble de la cl. Cette forme normale concerne les entits dont la cl est composite. Dans ce cas, chaque attribut doit dpendre de tous les attributs formant la cl. Par exemple, dans notre exemple de magasin, lentit VenteProduit reprsente un produit vendu lors dun acte de vente. Sa cl est composite : une cl de produit, une cl de vente. Nous voulons connatre le prix de vente unitaire du produit, ainsi que le prix dachat au fournisseur, pour connatre notre marge. O devons-nous mettre ces attributs ? Sur la figure 4.2, ils ont t placs dans lentit VenteProduit.

Figure 4.2 Deuxime modle Commerce

Est-ce logique ? Le prix dachat au fournisseur dpend-il de toute la cl, cest-dire du produit et de la vente ? Certainement pas : le prix dachat ne dpend pas de la vente effectue, moins que nous grions notre stock en flux tendu, et que nous commandions le produit chez notre fournisseur chaque commande du client. ce moment, peut-tre ngocions-nous un prix particulier selon la quantit commande ? Alors, cela aurait peut-tre du sens de placer le prix dachat dans cette entit. Mais dpendrait-il rellement de la vente ? Si nous ralisons plusieurs ventes le mme jour, et que nous effectuons une commande groupe auprs du fournisseur, il sagit alors plus dune notion de commande fournisseur, qui peut tre lie la vente, comme par exemple sur la figure 4.3.

4.1 Modlisation de la base de donnes

53

Figure 4.3 Troisime modle Commerce

Mais en faisant cela, nous rendons la cl de la vente, partie de la cl de la commande, ce qui signifie que nous ne pouvons crer de commande que pour une seule vente. Nous devons donc crer une entit intermdiaire, comme la figure 4.4.

Figure 4.4 Quatrime modle Commerce

La cl qui compose lentit CommandeFournisseurVenteProduit devient longue. Cest un problme si elle doit tre rpercute dans des tables filles en tant que cl trangre, et cest un atout si nous devons identifier rapidement partir de cette entit, dans le code, la rfrence de nos entits lies.

54

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Prvision Dans la phase de modlisation conceptuelle, on ne rflchit pas lutilit des cls par rapport aux requtes dextraction. Dans la modlisation physique, il devient utile, voir ncessaire de le faire, pour intgrer les performances des requtes dj au niveau de la modlisation cest lobjet de cet ouvrage. Nous pensons quil est trs utile davoir en tte les facilits apportes nos requtes, et notre recherche dinformation, pour intgrer ces besoins et ces contraintes dans notre modlisation physique.

Malheureusement, ce modle est loin dtre parfait. Nous avons, notamment, deux quantits : une quantit de produits commands, et une quantit de produits vendus. Ne peut-on pas dduire le nombre de produits commands en additionnant le nombre de produits vendus ? Si oui, il est important de supprimer lattribut Quantite de lentit CommandeFournisseur. Pourquoi ? Car si nous la conservons, la question sera : o aller chercher linformation ? Si plus tard, laide dune requte, nous trouvons une diffrence entre la quantit de produits commands et la somme des produits vendus, quelle est la quantit qui nous donne ce qui sest vraiment pass dans la ralit ? Par contre, si nous navons que lattribut Quantite de lentit VenteProduit, que se passe-t-il si une vente est annule par le client aprs sa passation de commande, ou si le client modifie sa quantit ? Comment savoir quelle est la quantit de produits rellement commande ? Ici, le modle doit tre complt : si le client modifie la quantit de produits voulus, il nous faut un moyen den conserver lhistorique, peut-tre en amendant la vente, ou en crant une deuxime vente lie la premire, par une cl rcursive. Et si la vente est annule, il nous faut certainement galement un moyen dexprimer dans notre schma ce quil advient des produits commands, afin de ne pas en perdre la trace, et de pouvoir les attribuer des ventes ultrieures, pour grer efficacement notre stock.

Troisime forme normale (3FN) Lentit doit respecter la 2FN. Tous les attributs doivent tre des faits concernant directement la cl, rien que la cl. Ils ne doivent en aucun cas dpendre dun attribut non-cl. Dans notre exemple prcdent, si lentit Vente contient un attribut magasin, peut-on dire quil viole la 3FN ? Douteux parce que la vente a bien lieu dans un magasin ? Certes, les deux informations sont lies, mais le magasin dpendil de la vente (la cl), et seulement de la vente ? Non, parce que dans le monde que nous modlisons, le magasin dpend aussi dun attribut non-cl : lemploy, car nous savons que cet employ est attribu un magasin. Il viole donc la 3FN. En ajoutant des attributs qui dpendent dun attribut non-cl, nous sommes certains dintroduire de la redondance dans le modle. Ici, lidentifiant de magasin na besoin dtre crit quune seule fois, dans lentit Employ, et non pas chaque vente.
Un signe courant et vident de violation de la 3NF, est la prsence dans une entit dattributs numrots. Des attributs tels que Adresse1, Adresse2, Adresse3 peuvent la rigueur tre douloureusement accepts, mais AdressePrivee1, AdressePrivee2, AdressePrivee3, AdresseProfessionnelle1, AdresseProfessionnelle2, AdresseProfessionnelle3, est clairement un problme. Il

4.1 Modlisation de la base de donnes

55

est temps de crer une entit Adresse, avec un attribut discriminant, spcifiant le type dadresse. La cration de squences dattributs est un cercle vicieux : vous avez limpression dy mettre un doigt, et bientt vous y avez enfonc le bras tout entier. chaque besoin nouveau, vous vous retrouvez ajouter une nouvelle colonne, avec un incrment de squence. Que ferez-vous lorsque vous en serez arriv la colonne Propriete20, ou Telephone12 ? Modlisez votre base correctement, ou si vous devez maintenir un modle existant, refactorez .

Refactoring Le concept de refactoring indique les techniques de modification du code tout au long de son existence. Il faut, en matire de modle de donnes, songer modifier la structure lorsque des nouveaux besoins se font sentir. Il faut rsister lenvie de coder en dur des modifications de logique, pour viter de rcrire ses requtes aprs modification de schma. Adapter son schma est plus long, mais bien plus solide et volutif.

Lorsque la base est normalise, il est parfois utile de dnormaliser, cest--dire de dupliquer au besoin et uniquement lorsque cest absolument ncessaire le contenu dune colonne. La plupart du temps, dnormaliser nest pas ncessaire. Souvent les dnormalisations sont faites avant mme de tester les performances des requtes, ou pour rsoudre des problmes de performances qui proviennent dune mauvaise criture de code SQL. Mme si la dnormalisation se rvle utile, des mcanismes de SQL Server comme les colonnes calcules ou les vues indexes permettent de dupliquer logiquement les donnes sans risque dincohrences. Lautre solution pour dnormaliser consiste crer des colonnes physiques qui vont contenir des donnes provenant dautres tables, et qui sont maintenues par code, en gnral par dclencheur ou procdure stocke. Cette approche comporte quelques inconvnients : ajouter du code augmente les risques derreur, diminue les performances gnrales et complique la gestion de la base. Nous vous conseillons en tout cas vivement, dans ce cas, dutiliser une mthodologie simple, claire et consistante. Crez par exemple des dclencheurs, avec une convention de dnomination qui permet de les identifier rapidement, et optimisez leur code. Une chose en tout cas est importante : la dnormalisation nest pas la rgle, mais tout fait lexception. Elle ne doit tre mene quen dernier ressort. En tout cas, comme son nom lindique, elle consiste modifier un schma dj normalis. En aucun cas, elle ne peut tre une faon prliminaire de btir son modle. Logiquement, plus votre modle est normalis, plus le nombre de tables augmente, et moins vous avez de colonnes par table. Cela a de nombreux avantages de performance : vous diminuez le nombre de donnes dupliques, donc le volume de votre base, vous rduisez le nombre de lectures ncessaires pour extraire un sousensemble de donnes (on cherche en gnral rcuprer une partie seulement des lignes et colonnes, pas une table tout entire), vous augmentez la possibilit de crer des index clustered (plus sur ce concept dans le chapitre 6), et donc la capacit obtenir des rsultats tris sans des oprations de tri coteuses et gnrer des plans

56

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

dexcution utilisant loprateur de jointure de fusion (merge join), un algorithme de jointure trs rapide, etc. Citoyens, nayez pas peur de la normalisation ! Ne tombez pas dans le pige consistant croire que laugmentation du nombre de tables va rendre votre code SQL compliqu, et va faire baisser les performances. Crer des tables qui ne respectent pas au moins les trois premires formes normales vous entranera dans une multitude de problmes avec lesquels vous serez oblig de vivre quotidiennement. Prenez ds le dbut de bonnes habitudes. En ce qui concerne la complexit, vous pouvez la dissimuler par la cration de vues, qui sont, du point de vue du modle relationnel, conceptuellement identiques aux tables.
Pour aller plus loin, nous vous conseillons de vous procurer un bon ouvrage sur la modlisation relationnelle, comme par exemple UML 2 pour les bases de donnes , de Christian Soutou.

Modle OLAP
En 1993, Edward F. Codd publia un livre blanc nomm Providing OLAP (On-line Analytical Processing) to User-Analysts: An IT Mandate , dans le but de prciser les exigences dapplications destines lanalyse de vastes volumes de donnes, ce quon appelle aujourdhui de plusieurs termes : systmes daide la dcision (decision support system, DSS), business intelligence, dcisionnel, etc. Malgr la lgre polmique autour de cet article, commandit par Hyperion (lentreprise sappelait alors Arbor Software) donc peut-tre partiel, sans doute plus crit par ses collaborateurs que par Codd lui-mme, et en tout cas non support par Chris Date, lassoci de Edward Codd, lcrit popularisa le terme OLAP, par opposition OLTP. Un systme OLTP (OnLine Transactional Processing) est en perptuelle modification, les oprations qui sy excutent sont encapsules dans des transactions, ce qui permet un accs multi-utilisateurs en lecture comme en criture. Cest pour ce genre de systme que le modle relationnel, les douze lois de Codd (qui sont en ralit au nombre de treize) et les formes normales furent conus. Un systme OLAP (OnLine Analytical Processing) est utilis principalement en lecture, pour des besoins danalyse et dagrgation de larges volumes de donnes. Alors que les requtes OLTP se concentrent en gnral sur un nombre rduit de lignes, les requtes analytiques doivent parcourir un grand ensemble de donnes pour en extraire les synthses et les tendances. Les systmes OLAP sont en gnral implments en relation avec des entrepts de donnes (data warehouses) parfois constitus en succursales nommes data marts. Ces entrepts centralisent toutes les donnes (oprationnelles, financires, etc.) de lentreprise (ou du groupe) souvent avec une perspective historique provenant de tous les services, pour offrir une vision globale de lactivit et des performances (figure 4.5).

4.1 Modlisation de la base de donnes

57

Figure 4.5 Les entrepts de donnes

Pour faciliter lanalyse des donnes, une reprsentation dite multi-dimensionnelle, sous forme de cubes , sest dveloppe. Il sagit de prsenter les indicateurs chiffrs (donnes de ventes, quantits, prix, ratios, etc.), quon appelle aussi mesures, en les regroupant sur des axes danalyse multiples, nomms dimensions. Les agrgations (regroupements) sont effectues travers ces dimensions. On peut par exemple vouloir afficher les donnes de ventes par mois, par types de produits, sur les magasins dun dpartement donn, pour un fournisseur. Le modle relationnel normalis nest pas optimal pour cette forme de requtes sur des volumes trs importants de donnes. De cette constatation, deux modles principaux propres lOLAP se sont dvelopps : le modle en toile et le modle en flocon. Le modle en toile (star schema, figure 4.6), fortement dnormalis, prsente une table centrale des indicateurs (une table de faits, ou fact table), qui contient outre les mesures les identifiants de liaison chaque dimension. Les tables de dimensions entourent la table de faits. Elles peuvent contenir des colonnes fortement redondantes. On peut par exemple, dans une dimension client, trouver des colonnes Ville et Pays qui contiennent les noms de lieu en toutes lettres, dupliqus des dizaines ou des centaines de milliers de fois. Les erreurs de saisie ne peuvent pas se produire, puisque ces tables sont alimentes exclusivement par des processus automatiques, et les lectures en masse sont facilites par llimination de la ncessit de suivre les jointures. La volumtrie nest pas non plus un problme, ces systmes tant btis autour de solutions de stockage adaptes.

58

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Figure 4.6 Le modle en toile

Le modle en flocon (snowflake schema, figure 4.7) est une version du modle en toile qui conserve, sur certaines dimensions, des relations dans le but de limiter le volume de stockage ncessaire. Il peut tre utilis si la quantit de donnes dupliques est vraiment trop importante. En gnral, un modle en toile est prfrable.

Figure 4.7 Le modle en flocon

Cet ouvrage se concentre sur loptimisation dun systme OLTP. Nous tenions toutefois vous prsenter les fondamentaux de lOLAP afin de vous donner lorientation ncessaire au cas o vous auriez modliser un entrept de donnes.
Pour aller plus loin sur le sujet, un livre fait rfrence : Entrepts de donnes. Guide pratique de modlisation dimensionnelle , 2e dition, de Ralph Kimball et Margy Ross, aux ditions Vuibert informatique (dition anglaise : The Data Warehouse

4.1 Modlisation de la base de donnes

59

Toolkit: The Complete Guide to Dimensional Modeling, chez Wiley). Ralph Kimball est un des deux thoriciens importants du dcisionnel moderne, et les principes de ce livre ont t appliqus dans les outils BI de SQL Server.

4.1.3 Bien choisir ses types de donnes


Lors du passage au modle physique de donnes (MPD), cest--dire lors de ladaptation du MCD une implmentation de SGBDR particulire SQL Server en ce qui nous concerne , plusieurs considrations sont importantes pour les performances : Choisissez les bons types de donnes. Nous avons vu que la 1FN prescrit de ddier chaque attribut une valeur atomique. Physiquement, il est galement important de ddier le type de donnes adapt cette valeur. Par exemple, si vous devez exprimer un mois de lanne, faut-il utiliser un type DATE (ou DATETIME en SQL Server 2005), ou un CHAR(6), dans lequel vous inscrirez par exemple '200801' pour le mois de janvier 2008 ? Si lide vous vient dutiliser un CHAR(6), que ferez-vous lorsquil vous sera demand de retourner par requte la diffrence en mois entre deux dates ? Si vous avez opt ds le dpart pour le type qui convient, la fonction DATEDIFF() est tout ce dont vous avez besoin. De plus, le type DATE est cod sur trois octets, alors quun CHAR(6), par dfinition, occupe le double. Sur une table de dix millions de lignes, cela reprsente donc un surpoids denviron trente mgaoctets, totalement inutile et contre performant.

Prvoir la volumtrie la mise en place dune nouvelle base de donnes, estimez sa volumtrie future, pour dimensionner correctement votre machine, vos disques et la taille de vos fichiers de donnes et de journal. Vous pouvez utiliser les fonctionnalits intgres de dimensionnement des outils de modlisation comme Embarcadero ER/Studio ou Sybase PowerDesigner/PowerAMC. HP publie aussi des outils de sizing : http://activeanswers.compaq.com/ActiveAnswers/ Render/1,1027,4795-6-100-225-1,00.htm. Vous pouvez aussi crer votre propre script, par exemple fond sur la colonne max_length de la vue sys.columns, en prenant en compte que la valeur de max_length pour un objet large est -1.

Une colonne comportant un type de taille trop importante augmente la taille de la ligne. Il sensuit que moins de lignes peuvent rsider dans la mme page, et plus de pages doivent tre lues, ou parcourues, pour obtenir le mme jeu de donnes. Il est important, dans ce cadre, de faire la distinction entre types prcis et types approchs, types de taille fixe ou de taille variable, ainsi quentre les types qui restent dans la page et ceux qui peuvent sen chapper. Passons en revue quelques types de donnes systme de SQL Server.

Numriques
Les types numriques sont soit entiers, soit dcimaux, virgule fixe ou flottante. Les types entiers sont cods en un bit (BIT), un octet (TINYINT), deux octets (SMALLINT), quatre octets (INT) ou huit octets (BIGINT). Le BIT est utilis en gnral pour exprimer des valeurs boolennes (vrai ou faux). Il accepte le marqueur NULL. Le stoc-

60

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

kage des colonnes de type BIT est optimis par le regroupement de plusieurs de ses colonnes dans un seul octet de stockage. Ainsi, si vous avez de une huit colonnes de type BIT, SQL Server utilisera un octet dans sa page de donnes, de neuf seize, deux octets, etc. Vous pouvez donc crer plusieurs de ces colonnes sans inquitude. Notez que les chanes de caractres TRUE et FALSE sont convertibles en BIT, explicitement ou implicitement, ce qui est utile pour reproduire dans son code un comportement proche dun type boolen. Si vous pouvez travailler directement en 0 ou 1, faites-le, cela vitera la phase de conversion :
-- ok SELECT CAST('TRUE' as bit), CAST('FALSE' as bit) SET LANGUAGE 'french' -- erreur SELECT CAST('VRAI' as bit), CAST('FALSE' as bit) -- ok DECLARE @bool bit SET @bool = 'TRUE' SELECT @bool

Une autre option est de crer une colonne de type entier ou BINARY, et y stocker des positions 0 ou 1, bit par bit (en crant une colonne utilise comme bitmap, champ de bits). Les oprateurs T-SQL de gestion des bits (bitwise operators) permettent de manipuler chaque position. Cest un moyen efficace de grer un tat complexe, car il permet dexprimer la fois des positions particulires, et une vue gnrale, indexable, de la situation de la ligne. Une recherche sur un tat complexe pourra profiter de cette approche. En revanche, une recherche impliquant seulement une position de bits sera moins efficace. Prenons un exemple :
USE tempdb GO CREATE TABLE dbo.PartiPolitique ( code char(5) NOT NULL PRIMARY KEY CLUSTERED, nom varchar(100) NOT NULL UNIQUE, type int NOT NULL DEFAULT(0) ) GO INSERT SELECT SELECT SELECT SELECT SELECT SELECT SELECT SELECT SELECT SELECT SELECT INTO dbo.PartiPolitique (code, nom) 'PCF', 'Parti Communiste Franais' UNION ALL 'MRC', 'Mouvement Rpublicain et Citoyen' UNION ALL 'PS', 'Parti Socialiste' UNION ALL 'PRG', 'Parti Radical de Gauche' UNION ALL 'VERTS', 'Les Verts' UNION ALL 'MODEM', 'Mouvement Dmocrate' UNION ALL 'UDF', 'Union pour la Dmocratie Franaise' UNION ALL 'PSLE', 'Nouveau Centre' UNION ALL 'UMP', 'Union pour un Mouvement Populaire' UNION ALL 'RPF', 'Rassemblement pour la France' UNION ALL 'DLR', 'Debout la Rpublique' UNION ALL

4.1 Modlisation de la base de donnes

61

SELECT 'FN', 'Front National' UNION ALL SELECT 'LO', 'Lutte Ouvrire' UNION ALL SELECT 'LCR', 'Ligue Communiste Rvolutionnaire' GO CREATE INDEX nix$dbo_PartiPolitique$type ON dbo.PartiPolitique ([type]) GO -- droite UPDATE dbo.PartiPolitique SET type = type | POWER(2, 0) WHERE code in ('MODEM', 'UDF', 'PSLE', 'UMP', 'RPF', 'DLR', 'FN', 'CPNT') -- gauche UPDATE dbo.PartiPolitique SET type = type | POWER(2, 1) WHERE code in ('MODEM', 'UDF', 'PS', 'PCF', 'PRG', 'VERTS', 'LO', 'LCR') -- antilibral UPDATE dbo.PartiPolitique SET type = type | POWER(2, 2) WHERE code in ('PCF', 'LO', 'LCR') -- au gouvernement UPDATE dbo.PartiPolitique SET type = type | POWER(2, 3) WHERE code in ('UMP', 'PSLE') -- qui est au centre ? -- scan SELECT code FROM dbo.PartiPolitique WHERE type & (1 | 2) = (1 | 2) -- seek... mais incorrect SELECT code FROM dbo.PartiPolitique WHERE type = 3

Les deux dernires requtes utilisent deux stratgies de recherche diffrentes. Elles produisent un plan dexcution trs diffrent galement, car la premire requte ne peut pas utiliser dindex pour laider. Reportez-vous la section 6.2 traitant du choix des index, pour plus de dtails. Vous pouvez aussi crer une table de rfrence indiquant quelle est la valeur de chaque bit, et grer dans votre code des variables de type INT pour exprimer lisiblement quelles sont les valeurs de chaque bit. Mais dans cet ouvrage ddi aux performances, nous devons prciser que ces mthodes sont souvent une facilit de code qui diminue la performance des requtes.

62

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

En rgle gnrale, une cl technique, auto-incrmentale (IDENTITY) ou non, sera code en INT. Lentier 32 bits permet dexprimer des valeurs jusqu 2^31-1 (2 147 483 647), ce qui est souvent bien suffisant. Si vous avez besoin dun peu plus, comme les types numriques sont signs, vous pouvez disposer de 4 294 967 295 valeurs en incluant les nombres ngatifs dans votre identifiant. Vous pouvez aussi indiquer le plus grand nombre ngatif possible comme semence (seed) de votre proprit IDENTITY, pour permettre la colonne auto-incrmentale de profiter de cette plage de valeurs largie :
CREATE TABLE dbo.autoincrement (id int IDENTITY(-2147483648, 1)).

Pour les identifiants de tables de rfrence, un SMALLINT ou un TINYINT peuvent suffire. Il est tout fait possible de crer une colonne de ces types avec la proprit IDENTITY. Noubliez pas que les identifiants sont en gnral les cls primaires des tables. Par dfaut, la cl primaire est clustered (voir les index clustered en section 6.1). Lidentifiant se retrouve donc trs souvent cl de lindex clustered de la table. La cl de lindex clustered est incluse dans tous les index nonclustered de la table, et a donc une influence sur la taille de tous les index. Plus un index est grand, plus il est coteux parcourir, et moins SQL Server sera susceptible de lutiliser dans un plan dexcution.

Chanes de caractres
Les chanes de caractres peuvent tre exprimes en CHAR ou en VARCHAR pour les chanes ANSI, et en NCHAR ou NVARCHAR pour les chanes UNICODE. Un caractre UNICODE est cod sur deux octets. Un type UNICODE demandera donc deux fois plus despace quun type ANSI. La taille maximale dun (VAR)CHAR est 8000. La taille maximale dun N(VAR)CHAR, en toute logique, 4000. La taille du CHAR est fixe, celle du VARCHAR variable. La diffrence rside dans le stockage. Un CHAR sera toujours stock avec la taille dclare la cration de la variable ou de la colonne, et un VARCHAR ne stockera que la taille relle de la chane. Si une valeur infrieure la taille maximale est insre dans un CHAR, elle sera automatiquement complte par des espaces (caractre ASCII 32). Ceci concerne uniquement le stockage, les fonctions de chane de T-SQL et les comparaisons appliquant automatiquement un RTRIM au contenu de variables et de colonnes. Dmonstration :
DECLARE @ch char(20) DECLARE @vch varchar(10) SET @ch = '1234' SET @vch = '1234 ' -- on ajoute deux espaces SELECT ASCII(SUBSTRING(@ch, 5, 1)) 32 = espace SELECT LEN (@ch), LEN(@vch) - les deux donnent 4 IF @ch = @vch PRINT 'c''est la mme chose' - eh oui, c'est la mme chose SELECT DATALENGTH (@ch), DATALENGTH(@vch) -- @vch = 6, les espaces sont prsents.

4.1 Modlisation de la base de donnes

63

SET @vch = @ch SELECT DATALENGTH(@vch) - 20 !

Ce comportement est toujours valable dans le cas de chanes UNICODE, ou lors du mlange de chanes ANSI avec des chanes UNICODE, sauf dans le cas du LIKE ! Dans ce cas, les espaces finaux sont importants. Il sagit dune conformit avec la norme SQL-92 (implmente seulement pour UNICODE) :
DECLARE @vch varchar(50) DECLARE @nvch nvarchar(100) SET @vch = 'salut!' SET @nvch = 'salut! ' IF @vch = @nvch PRINT 'ok'; -- a marche... IF @vch LIKE @nvch PRINT 'ok' a ne marche pas...

Donc, utilisez les '%' pour obtenir des rsultats conformes vos attentes dans ces cas. Quen est-il donc de lutilit du RTRIM dans la rcupration ou linsertion de donnes ? Le RTRIM est inutile dans un traitement de CHAR, puisque le moteur de stockage ajoute de toute manire des espaces pour complter la donne. Par contre, pour linsertion ou laffichage de VARCHAR, un RTRIM peut tre utile si vous souponnez que les chanes insres peuvent contenir des espaces. Comme nous le voyons dans lexemple, les espaces explicites la fin dune chane sont conservs dans le VARCHAR. Cela inclut le cas o vous copiez une valeur dun type CHAR vers un type VARCHAR : les espaces sont conservs. Mfiez-vous de ne pas alourdir inutilement vos tables en oubliant ce dtail lors de limportation ou de lchange de donnes.

Note Ce comportement correspond au cas o loption SET ANSI_PADDING tait active lors de la cration de la colonne ou de la variable. Cette option est par dfaut ON, et il est recommand de la laisser ainsi, pour garantir un comportement cohrent des colonnes, en accord avec la norme SQL.

Vous pouvez vrifier vos colonnes de type VARCHAR avec une requte comme celle-ci :
SELECT AVG(DATALENGTH(EmailAddress)) as AvgDatalength, AVG(LEN(EmailAddress)) as AvgLen, SUM(DATALENGTH(EmailAddress)) - SUM(LEN(EmailAddress)) as Spaces, COLUMNPROPERTY(OBJECT_ID('Person.Contact'), 'EmailAddress', 'precision') as ColumnLength FROM Person.Contact;

64

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

qui vous donne le remplissage moyen de la colonne, contre la taille moyenne relle, et la diffrence en octets totaux. Attention, cette requte nest valable que pour les colonnes ANSI. Divisez DATALENGTH() par deux, dans le cas de colonnes UNICODE. Comment connatre les colonnes de taille variable dans votre base de donnes ? Une requte de ce type va vous donner la rponse :
SELECT QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) as tbl, QUOTENAME(COLUMN_NAME) as col, DATA_TYPE + ' (' + CASE CHARACTER_MAXIMUM_LENGTH WHEN -1 THEN 'MAX' ELSE CAST(CHARACTER_MAXIMUM_LENGTH as VARCHAR(20)) END + ')' as type FROM INFORMATION_SCHEMA.COLUMNS WHERE DATA_TYPE LIKE 'var%' OR DATA_TYPE LIKE 'nvar%' ORDER BY tbl, ORDINAL_POSITION;

Vous pouvez aussi utiliser la fonction COLUMNPROPERTY().

En conclusion, la rgle de base est assez simple : utilisez le plus petit type de donnes possible. Si votre colonne na pas besoin de stocker des chanes dans des langues ncessitant lUNICODE (chinois, japonais, etc.) crez des colonnes ANSI. Elles seront raisonnablement valables pour toutes les langues utilisant un alphabet latin. Elles vous feront conomiser 50 % despace. Certains outils Microsoft ont tendance crer automatiquement des colonnes UNICODE. Nhsitez pas changer cela laide dun ALTER TABLE par exemple. Il ny a pratiquement aucun risque deffet de bord dans votre code SQL, part lutilisation de DATALENGTH ou de LIKE, comme nous lavons vu prcdemment. La diffrence de temps daccs par le moteur de stockage entre une colonne fixe ou variable, en termes de recherche de position de la chane, est ngligeable. En revanche, la diffrence de volume de donnes transmettre lors dun SELECT, peut peser sur les performances. Comme rgle de base, nutilisez des CHAR que pour des longueurs infrieures 10 octets, et de prfrence sur des colonnes dans lesquelles vous tes srs de stocker toujours la mme taille de chane (par exemple, pour une colonne de codes ISO de pays, ne crez pas un VARCHAR(2), mais un CHAR(2), car le VARCHAR cre deux octets supplmentaires pour le pointeur qui permet dindiquer sa position dans la ligne). vitez les CHAR de grande taille : vous risquez de remplir inutilement votre fichier de donnes. Imaginons que vous criez une table ayant un CHAR(5000) ou un NCHAR(2500), vous auriez ainsi la garantie que chaque page de donnes ne peut contenir quune ligne. Si vous ne stockez que dix caractres dans cette colonne, vous avec besoin, pour dix octets, doccuper en ralit 8 060 octets

4.1 Modlisation de la base de donnes

65

Inversement, des colonnes variables peuvent provoquer de la fragmentation dans les pages ou dans les extensions, selon que les mises jour modifient la taille de la ligne. Donc, songez tester rgulirement la fragmentation, et dfragmenter vos tables lorsque ncessaire. Nous aborderons ce point dans le chapitre 6.

Valeurs temporelles La plupart des bases de donnes doivent stocker des donnes temporelles. En SQL Server 2005, vous disposez de deux types de donnes : DATETIME et SMALLDATETIME. Ne confondez pas avec TIMESTAMP, qui contrairement dautres SGBDR et la norme SQL, ne correspond pas dans SQL Server un type de donnes de date, mais un compteur unique par base de donnes, incment automatiquement la modification de toute colonne portant ce type de donnes. DATETIME est cod sur huit octets, quatre octets stockant la date, et quatre lheure. SMALLDATETIME est cod sur quatre octets, deux octets stockant la date, et deux lheure. Cela permet au type DATETIME de couvrir une priode allant du 1er janvier 1753 au 31 dcembre 9999, avec une prcision de la partie heure de 3,33 millisecondes. Le type SMALLDATETIME va du 1 janvier 1900 au 6 juin 2079, avec une prcision la minute. Il ny a pas, en SQL Server 2005, de possibilit de stocker une date sparment dune heure, sauf ladapter dans un autre type de donnes (en CHAR par exemple). Cela reste trs peu utilis en pratique, car posant le problme de la manipulation des dates : comment trouver le nombre de jours sparant deux CHAR ? Lhabitude prise est donc dexprimer des dates seulement en spcifiant une heure de 00:00:00, et les heures seules en passant une date au 1er janvier 1900. Le type SMALLDATIME est deux fois plus court que le type DATETIME et est donc prfrer tant que possible. Il est idal pour exprimer toute date/heure dont la granularit est au-dessus de la minute. Si vous devez stocker des temps plus prcis, comme par exemple des lignes de journalisation, nutilisez surtout pas SMALLDATETIME : il vous serait impossible de trier correctement les lignes lintrieur de la mme minute. Il fut un temps o certains langages client, comme Visual Basic, ne manipulaient pas correctement le type SMALLDATETIME. Ce temps est rvolu depuis longtemps. Le type DATETIME est plus prcis, mais pas exactement prcis. Mfiez-vous de son arrondi : vous pouvez passer, en chane de caractres, une valeur de temps en millisecondes, qui sera arrondie automatiquement par SQL Server, ce qui peut vous rserver des surprises. Imaginez que votre application cliente soit prcise la milliseconde, et insre deux lignes, deux millisecondes prs. Pour SQL Server, la valeur DATETIME sera la mme, comme le prouve lexemple suivant :
DECLARE @date1 datetime DECLARE @date2 datetime SET @date1 = '20071231 23:59:59:999' SET @date2 = '20080101 00:00:00:001' IF @date1 = @date2 PRINT 'pas de diffrence'

66

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Pour SQL Server, ces deux dates correspondent au 1er janvier 2008, minuit exactement (2008-01-01 00:00:00.000). Attention donc galement aux comparaisons. Puisque les types DATETIME et SMALLDATETIME contiennent toujours la partie heure, une comparaison doit se faire aux 3 millisecondes, ou la minute prs. Cest un problme rcurrent de performances, car il faut souvent convertir la valeur de la colonne pour lui donner lheure 00:00:00.000 dans la clause WHERE, ce qui empche toute utilisation dindex. Si vous savez que vous avez ce type de recherche faire, stockez systmatiquement vos DATETIME avec la partie horaire 0, ou crez une colonne calcule indexe qui reprend la date seulement, et recherchez sur cette colonne. Avec SQL Server 2008, le choix est plus tendu. SQL Server 2008 ajoute plusieurs nouveaux types de donnes temporels : DATE stocke seulement la date, sur trois octets ; TIME stocke seulement la partie horaire. TIME est beaucoup plus prcis que la partie heure dun DATETIME. Sa plage de valeurs va de 00:00:00.0000000 23:59:59.9999999. Sa prcision est donc de 100 nanosecondes, sur trois cinq octets ; DATETIME2 stock sur huit octets, DATETIME2 permet une plus grande plage de valeurs que DATETIME. Sa plage de dates va de 0001-01-01 9999-12-31, avec une prcision de temps (paramtrable) 100 nanosecondes. Si vous diminuez la prcision, vous pouvez stocker un DATETIME2 sur six octets. Ce nouveau type a t introduit principalement par compatibilit avec les dates .NET, et avec la norme SQL ; DATETIMEOFFSET bas sur un DATETIME2, DATETIMEOFFSET ajoute une zone horaire. Cod sur huit dix octets.

Pour viter les problmes en rapport avec les formats de dates lis aux langues, passer les dates en ISO 8601 non spar : 'AAAAMMJJ'.

Type XML
SQL Server 2005 a introduit le type de donnes XML, qui fait partie de la norme SQL:2003. Une colonne ou variable XML peut contenir jusqu 2 Go dinstance XML bien forme ou valide, cod en UTF-16 uniquement. Une colonne XML peut tre valide par une collection de schma XSD (un schma, dans le monde XML, est la description de la structure dun document XML. Le sujet en soi mriterait un ouvrage). Voici un exemple de cration de collection de schmas :
CREATE XML SCHEMA COLLECTION [Person].[AdditionalContactInfoSchemaCollection] AS N'<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" ... </xsd:schema>'

4.1 Modlisation de la base de donnes

67

Le schma se voit dans la vue systme sys.xml_schema_collections, ou dans lexplorateur dobjets de SSMS, dans une base de donnes, dans le dossier Programmability/Types/XML Schema Collections. On lappelle une collection de schmas, parce que plusieurs schmas peuvent tre utiliss, de faon complmentaire, pour valider le mme document ou fragment. Vous pouvez ensuite crer une table qui contient une colonne de type XML, valide par cette collection de schmas :
CREATE TABLE [Person].[Contact]( [ContactID] [int] IDENTITY(1,1) NOT NULL, -- ... [AdditionalContactInfo] [xml]([Person].[AdditionalContactInfoSchemaCollection]) NULL );

Nous obtenons alors une colonne XML type, par opposition une colonne non valide par un schma, dite non type. La collection de schmas est une contrainte de colonne, au mme titre quune contrainte CHECK, par exemple. On pourrait se dire que la validation par le schma est une tape supplmentaire effectue par le moteur, et quelle est donc nuisible aux performances. En ralit, cest le contraire. Comme pour la contrainte CHECK, la vrification est effectue lcriture dans la colonne (et selon la complexit de la collection de schmas, cette tape peut se rvler coteuse), mais on lit en gnral plus souvent que lon crit. Par ailleurs, lextraction, SQL Server pourra sappuyer sur les informations fournies par la contrainte pour faciliter son travail. Le schma fournit des informations prcises sur le type de donnes des items dans le XML, ce qui permet SQL Server de stocker linstance XML sous une forme binaire (un blob XML) au lieu dune reprsentation textuelle, ce qui rend la colonne XML beaucoup plus compacte. Les valeurs atomiques sont aussi stockes avec leur type de donnes (linformation de type est donne dans la collection de schma), ce qui permet un parsing beaucoup plus efficace. Enfin, la vrification de la validit dune requte XQuery par rapport aux types de valeurs peut tre vrifie sa compilation, et des optimisations peuvent tre effectues. Le typage amliore aussi lefficacit des index XML1. Bref, cherchez autant que possible crer des colonnes XML types.

Vous pouvez indexer vos colonnes XML, ce qui cre une reprsentation interne en table relationnelle, et cre un index B-Tree sur cette structure. Pour plus dinformation sur la structure des index, reportez-vous la section 6.1.

Types divers
Il existe un type de donnes dont nous ne devrions mme pas parler ici, tant il est viter : sql_variant. Comme son nom lindique, il correspond un variant dans les langages comme Visual Basic, cest--dire quil adapte automatiquement son type
1. Plus dinfos dans larticle Technet Performance Optimizations for the XML Data Type in SQL Server 2005 sur http://msdn.microsoft.com/en-us/library/ms345118.aspx.

68

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

par rapport la valeur stocke. Cest videmment un type viter absolument, tant par son impact sur les performances que par son laxisme en terme de contraintes de donnes. Le type sql_variant peut contenir 8 016 octets, il est donc indexable vos risques et prils (une cl dindex suprieure 900 octets gnrera une erreur). On ne peut le concatner, ni lutiliser dans une colonne calcule, ou un LIKE... Bref, oubliez-le. Le type de donnes UNIQUEIDENTIFIER correspond un GUID (Globally Unique Identifier), une valeur code sur 16 octets gnrs partir dun algorithme qui la rend unique travers le monde et au del (le GUID nest pas garanti unique, mais les probabilits que deux GUID identiques puissent tre gnrs sont statistiquement infinitsimales). Vous voyez des GUID un peu partout dans le monde Microsoft, notamment dans la base de registre. On entend en gnral par le terme GUID, limplmentation par Microsoft de la norme UUID (Universally Unique Identifier), qui calcule notamment une partie de la chane avec ladresse matrielle (MAC) de lordinateur. Un exemple de GUID serait : CC61D24F-125B-4347-BB37-9E7908613279. Certaines personnes lutilisent pour gnrer des cls primaires de tables, profitant de lunicit globale du GUID pour obtenir une cl unique travers diffrentes tables ou bases de donnes, ou pour gnrer des identifiants non squentiels, par exemple pour un identifiant dutilisateur sur une application Web, et ainsi viter quun pirate ne devine lID dun autre utilisateur. En termes de performances, utiliser un UNIQUEIDENTIFIER en cl primaire est une trs mauvaise ide, plus forte raison si lindex sur la cl est clustered. Cela tient bien sr la taille de la donne (vous verrez dans le chapitre 6 que tous les index incorporent la cl de lindex clustered). Cest aussi un excellent moyen de fragmenter la table : si vous crez un index clustered sur une colonne o les donnes sinsrent des positions alatoires, vous provoquez une intense activit de sparation de pages (les pages doivent se sparer et dplacer une partie de leurs lignes ailleurs, pour accommoder les nouvelles lignes insres, plus de dtails dans le chapitre sur les index), donc une baisse de performances, ainsi quune forte fragmentation, do une nouvelle baisse de performances. Enfin, cela a de fortes chances daugmenter significativement la taille de votre base, puisquune cl primaire est souvent dplace dans plusieurs tables filles en cls trangres. Tout cela faisant 16 octets par cl, le stockage est plus lourd, les index plus gros, et les jointures plus coteuses pour les requtes.

Si vous songez utiliser des GUID dans une problmatique de bases de donnes disperses, optez plutt pour une cl primaire composite, deux colonnes : une colonne donnant un identifiant de serveur ou de base de donnes, et lautre identifiant un compteur IDENTITY propre la table. Vous aurez non seulement linformation de la base source de la ligne dans la cl, mais de plus celle-ci psera 5 ou 6 octets (un TINYINT ou SMALLINT pour lID de base, un INT pour lID de ligne) au lieu de 16.

4.1 Modlisation de la base de donnes

69

Vous gnrez les valeurs de GUID dans vos colonnes UNIQUEIDENTIFIER laide de la fonction T-SQL NEWID(), que vous pouvez placer dans la contrainte DEFAULT de la colonne. Si vous devez utiliser un UNIQUEIDENTIFIER et que vous voulez lindexer, vous pouvez utiliser (seulement en contrainte DEFAULT) la fonction NEWSEQUENTIALID(), qui gnre un GUID plus grand que tous ceux prsents dans la colonne. Cette fonction a t cre pour viter la fragmentation. Toutefois, si vous lutilisez, vous perdez lavantage de la gnration alatoire, et un pirate intelligent pourra facilement deviner des GUID prcdents. Dans les problmatiques didentifiant Web, il vaut mieux programmer dans lapplication cliente un algorithme de gnration de hachage partir des identifiants de la base de donnes, pour les protger des curieux. Cela permet de conserver de bonnes performances du ct SQL Server.

Objets larges
SQL Server proposait les types TEXT, NTEXT et IMAGE, pour stocker respectivement des objets larges de type chanes ANSI et UNICODE, et des objets binaires, comme des documents en format propritaire, des fichiers image ou son. Ces types sont toujours disposition, mais ils sont obsoltes et remplacs par (N)VARCHAR(MAX) et VARBINARY(MAX). Comme leurs anciens quivalents, ils permettent de stocker jusqu 2 Go de donnes (2^31 1 octets, prcisment), mais leur comportement est sensiblement diffrent. Ces types sont ce quon appelle des LOB (Large Objects, objets larges), bien que dans les BOL, vous verrez utiliser le nom valeurs larges (large values) pour les nouveaux types (le type XML en fait aussi partie). Ils sont stocks mme le fichier de donnes, dans des pages de 8 Ko organises dune faon particulire. Les types TEXT, NTEXT et IMAGE, conformment leur comportement dans SQL Server 2000, placent dans la ligne de la table un pointeur de 16 octets vers une structure externe la page. Cette structure est organise en arbre quilibr (B-Tree), comme un index, pour permettre un accs rapide aux diffrentes positions lintrieur du LOB. Lors de toute restitution de la ligne, le moteur de stockage doit donc suivre ce pointeur et rcuprer ces pages, mme si la colonne ne contient que quelques octets. En SQL Server 2000, nous avions un moyen de forcer SQL Server stocker dans la page elle-mme le contenu du LOB, sil ne dpassait pas une certaine taille, laide de la procdure stocke sp_tableoption. Cette option existe toujours sur SQL Server 2005 et peut tre utile pour optimiser des colonnes (N)TEXT. Par exemple :
EXEC sp_tableoption nomtable 'text in row', taille;

o taille est la taille en octets des donnes inclure dans la page, dans une plage allant de 24 7 000 (la chane 'ON' est aussi accepte, elle correspond une taille de 256 octets). Ds que cette option est active, si le contenu de la colonne est infrieur la taille indique, il est insr dans la page, comme une valeur traditionnelle. Pourquoi la limite infrieure est-elle de 24 octets ? Simplement parce que, quand 'text in row' est activ, le moteur de stockage ne place plus de pointeur vers la structure LOB. Si la donne insre est plus grande que la taille maximum spcifie, il inscrira dans la page la racine de la structure B-Tree du LOB, qui pse 24 bits.

70

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Pour dsactiver cette option, il suffit de fixer la taille 0, ou 'OFF' (attention, cela force la conversion de tous les BLOB existants, ce qui peut prendre un certain temps selon la cardinalit de la table). Cette opration ntant utile que pour les colonnes de type (N)TEXT et IMAGE, nous ne nous y attarderons pas. Utilisez les nouveaux types. Ceux-ci grent automatiquement linclusion dans la ligne lorsque le contenu peut tenir dans la page ( concurrence de 8 000 octets), en agissant alors comme un VARCHAR. Sil ne peut tenir dans la page, il est dplac dans une structure LOB, et agit donc comme un objet large. Le terme MAX permet dassurer lvolution vers des tailles de LOB suprieures dans les versions futures de SQL Server sans modifier la dfinition des tables. Ce fonctionnement reprsente le meilleur des deux mondes. Une autre option de table gre le comportement des valeurs larges ((N)VARCHAR(MAX), VARBINARY(MAX) et XML) :
EXEC sp_tableoption nomtable 'large value types out of row', ['ON'|'OFF'];

ON (1) Le contenu est toujours stock dans une structure LOB, la page ne contient que le pointeur 16 octets. OFF (0) Le contenu est stock dans la page sil peut y tenir. Il sagit donc du raisonnement inverse de 'text in row'. ON veut dire ici que le LOB est toujours extrieur la page. La valeur par dfaut est OFF. Vous pouvez inspecter les valeurs des deux options pour vos tables, avec la vue systme sys.tables :
SELECT SCHEMA_NAME(schema_id) + '.' + Name as tbl, text_in_row_limit, large_value_types_out_of_row FROM sys.tables ORDER BY tbl;

Il peut tre intressant de fixer 'large value types out of row' ON, au cas par cas. Si une table comporte une colonne de valeur large (un XML par exemple) qui stocke des structures qui ne sont que trs peu retournes dans les requtes dextraction, activer cette option permet de conserver des pages plus compactes, et donc doptimiser la plupart des lectures qui ne demandent pas le LOB. Lorsque loption 'large value types out of row' est change, dans un sens ou dans lautre, les lignes existantes ne sont pas modifies. Elles le seront au fur et mesure des UPDATE.

Dpassement de ligne
Dans le chapitre concernant la structure du fichier de donnes, nous avons dit quune ligne ne pouvait dpasser 8 060 octets, soit lespace disponible dans une page de donnes de 8 Ko. Nous venons de voir que le stockage des LOB permet de saffranchir de cette limite. Ce stockage comporte toutefois des inconvnients : la structure en B-Tree de gestion des LOB est un peu lourde lorsquil sagit de grer des valeurs qui vont jusqu 8 Ko, mais qui ne peuvent tenir dans la page cause de la

4.1 Modlisation de la base de donnes

71

taille des autres colonnes. Une solution ce problme pourrait tre deffectuer un partitionnement verticale, en crant une autre table contenant une colonne VARCHAR(8000) et lidentifiant, pour une relation un--un sur la premire table. partir de SQL Server 2005, ce nest plus ncessaire, un mcanisme automatique permet aux colonnes de longueur variable de dpasser la limite de la page. Cette fonctionnalit est appele dpassement de ligne (row overflow), elle ne sapplique quaux types variables (N)VARCHAR et VARBINARY, ainsi quaux types de donnes .NET. Prenons un exemple simple :
USE tempdb GO CREATE TABLE dbo.longcontact ( longcontactId int NOT NULL PRIMARY KEY, nom varchar(5000), prenom varchar(5000) ) GO INSERT INTO dbo.longcontact (longcontactId, nom, prenom) SELECT 1, REPLICATE('N', 5000), REPLICATE('P', 5000) GO

Avant SQL Server 2005, la cration de cette table aurait t accepte, mais avec laffichage dun avertissement disant que linsertion dune ligne dpassant 8 060 octets ne pourrait aboutir. Et dans les faits, lINSERT aurait dclench une erreur. En SQL Server 2005, tout fonctionne comme un charme. Observons ce qui a t stock :
SELECT p.index_id, au.type_desc, au.used_pages FROM sys.partitions p JOIN sys.allocation_units au ON p.partition_id = au.container_id WHERE p.object_id = OBJECT_ID('dbo.longcontact');

Linsertion sest donc droule sans problme pour une ligne dune taille totale de 10 004 octets (2 5 000, plus 4 octets dINTEGER). linsertion, SQL Server a automatiquement dplac une partie de la ligne dans une autre page, cre la vole, qui sert uniquement continuer la ligne. Cette page est dun type spcial, comme nous lavons vu dans la requte sur sys.allocation_units. Alors que la ligne est normalement dans une page de type In-row data, le reste doit tre plac dans une page de type Row-overflow data. Nous voyons quil y a quatre pages en tout, dont deux pages de row overflow. Pourquoi deux pages, alors que nous navons ajout que de quoi remplir une seule page de dpassement ? Vrifions laide de DBCC IND :
DBCC IND ('tempdb', 'longcontact', 1);

Nous voyons le rsultat sur la figure 4.8. Pour chaque type de page, nous avons une page de type 10, cest--dire une page IAM (Index Allocation Map), en quelque sorte la table des matires du stockage. Tout sexplique.

72

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Figure 4.8 Rsultat de DBCC IND

Essayons maintenant ceci :


UPDATE dbo.longcontact SET prenom = 'Albert' WHERE longcontactId = 1;

Et relanons la requte sur sys.allocation_units. Nous navons plus que trois pages : lIAM et la page de donnes pour lallocation In-row data, et seulement lIAM pour lallocation Row-overflow data. La page de row overflow a t supprime, toute la ligne est revenue dans la page. La page dIAM na pas t supprime, elle pourra tre rutilise au prochain dpassement.

SQL Server ne remet pas toujours la colonne dans la page In row lorsque la valeur diminue. Il ne vrifie que la nouvelle valeur peut tenir dans la page que si elle a diminu dau moins 1 000 octets, ce qui lui vite de trop frquentes vrifications.

Essayons autre chose :


TRUNCATE TABLE dbo.longcontact GO INSERT INTO dbo.longcontact (longcontactId, nom, prenom) SELECT 2, REPLICATE('N', 5000), REPLICATE('P', 3100) INSERT INTO dbo.longcontact (longcontactId, nom, prenom) SELECT 3, REPLICATE('N', 5000), REPLICATE('P', 3100) GO

Ici, nous forons linsertion de deux nouvelles lignes qui vont gnrer du dpassement, mais avec un remplissage de colonnes qui peut faire tenir deux lignes dans la mme page In-row ce que va faire SQL Server. Il va crer en effet une seule page In-row, et dplacer les deux colonnes nom dans deux pages de row overflow. Il faut noter que les pages de row overflow contiennent des valeurs entires. Une colonne ne peut pas tre rpartie sur plusieurs pages. Cest pour cette raison que le row overflow sapplique des types variables limits une taille de 8 000 octets (et une taille minimum de 24 octets, qui est lespace ncessaire pour stocker le pointeur vers la page de row overflow). Cela nous amne une dernire question : une page de row overflow peut-elle partager des donnes venant de plusieurs lignes, ou faut-il une nouvelle page pour chaque ligne en dpassement ? La seconde solution augmenterait le volume de stoc-

4.1 Modlisation de la base de donnes

73

kage de faon potentiellement importante. Vrifions ce qui se passe, laide dune table lgrement modifie :
CREATE TABLE dbo.troplongcontact ( troplongcontactId int NOT NULL PRIMARY KEY, nom char(8000), prenom varchar(5000) ) GO INSERT SELECT INSERT SELECT GO INTO dbo.troplongcontact 2, REPLICATE('N', 8000), INTO dbo.troplongcontact 3, REPLICATE('N', 8000), (troplongcontactId, nom, prenom) REPLICATE('P', 1000) (troplongcontactId, nom, prenom) REPLICATE('P', 1000)

Ici, nous ne conservons quune seule colonne de taille variable. Nous sommes donc certain de ne gnrer du row overflow que sur cette colonne. Que nous dit un DBCC IND ? Nous voyons le rsultat sur la figure 4.9.

Figure 4.9 Rsultat de DBCC IND

Nous voyons quatre pages In row, et deux pages Data overflow, de diffrents DataTypes. Ceux quon voit ici sont : 1 page de donnes. 2 page dindex. 3 page de LOB. 10 IAM.

Il y a donc deux pages de donnes In Row, et une page LOB. Observons la page LOB laide de DBCC PAGE :
DBCC TRACEON (3604) DBCC PAGE (tempdb, 1, 127, 3)

Dans le rsultat, nous trouvons ceci :


Blob Blob Blob Blob row at: Page (1:127) Slot 0 Length: 1014 Type: 3 (DATA) Id:9306112 row at: Page (1:127) Slot 1 Length: 1014 Type: 3 (DATA) Id:1128660992

74

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

La longueur correspond nos 1 000 octets, plus quelques octets supplmentaires. CQFD. La fonctionnalit de row overflow est bien pratique, mais quen est-il des performances ? Elles sont ncessairement moins bonnes. chaque lecture de page, le moteur de stockage doit aller chercher ailleurs dautres pages pour lire les donnes si des colonnes en row overflow sont demandes, ce qui provoque des lectures alatoires (random IO) supplmentaires. De plus, comme les pages de row overflow contiennent la totalit de la valeur dune colonne, cela gnre souvent de la fragmentation interne : les pages ne sont pas remplies de faon optimale. En un mot, rservez le row overflow des cas limits, pour grer un ventuel dpassement de page, pour des colonnes qui sont en gnral compactes mais peuvent de temps en temps contenir une chane plus longue.

Les statistiques de pages lues, retournes par SET STATISTICS IO ON, et la trace SQL, affichent diffremment les pages lues. La trace SQL montre, dans la colonne reads, le nombre total de pages lues (In row, Row overflow et LOB Data), y compris les pages dIAM. Les statistiques IO renvoyes dans la session sparent les pages In row des pages Row overflow et LOB. Voici un exemple de statistiques retournes pour une table contenant deux pages In row, deux pages de row overflow, et trois pages de LOB (VARCHAR(MAX)), pages IAM comprises : Table 'bigcontact'. Scan count 1, logical reads 2, physical reads 0, readahead reads 0, lob logical reads 5, lob physical reads 0, lob read-ahead reads 0. Lutilisation de SET STATISTICS IO ON permet donc dobtenir des rsultats plus prcis.

FILESTREAM
Le type FILESTREAM est nouveau dans SQL Server 2008. Cest une implmentation du type de donnes DATALINK de la norme SQL. SQL Server nest pas prvu pour grer efficacement le stockage et la manipulation dobjets larges, et maintenir des LOB en grande quantit pose des problmes de performances. Souvent, pour viter ces problmes, nous stockons les fichiers sur le systme de fichiers, et rfrenons le chemin dans une colonne VARCHAR. Le problme avec cette approche est quil faut en gnral ouvrir un partage sur le rseau, maintenir ces fichiers dune faon ou dune autre, et grer les sauvegardes de ces fichiers en plus des sauvegardes SQL. En grant des types FILESTREAM, vous crez un groupe de fichiers particulier pour les stocker, et ce groupe de fichiers utilise un stockage NTFS, plus adapt, plutt que le stockage traditionnel des fichiers de donnes. La cohrence transactionnelle sur ces fichiers est aussi assure. Pour utiliser FILESTREAM, vous devez dabord activer la fonctionnalit, laide de sp_configure :

4.1 Modlisation de la base de donnes

75

EXEC sp_configure 'filestream_access_level', '[niveau d'activation]' RECONFIGURE

et de SQL Server Configuration Manager (figure 4.10).

Figure 4.10 Configurer FILESTREAM

Vous pouvez galement employer la procdure stocke sp_filestream_configure, qui prend deux paramtres : @enable_level et @share_name. @enable_level indique le niveau dactivation : 0 dsactiv. 1 activ pour T-SQL uniquement : pas daccs des fichiers directs hors de SQL Server. 2 activ aussi pour laccs direct aux fichiers, en accs local seulement. 3 activ aussi pour laccs direct aux fichiers travers un partage rseau. @share_name permet de spcifier un nom de partage rseau, qui est celui de linstance SQL Server par dfaut. Ensuite, lancez la commande RECONFIGURE pour que les changements soient pris en compte.

Cette procdure va peut-tre disparatre la sortie dfinitive de SQL Server 2008, et tre remplace par une option dinstallation de linstance. Voir http:// www.sqlskills.com/blogs/bobb/2008/02/28/ ConfiguringFilestreamInCTP6ItsDifferent.aspx et les BOL si vous avez des problmes sa mise en uvre. De plus, dans la version bta, un redmarrage de lins-

76

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

tance est souvent ncessaire pour activer cette fonction. Cela pourrait tre amlior la sortie finale.

Vous crez ensuite un groupe de fichiers destin au FILESTREAM :


ALTER DATABASE AdventureWorks ADD FILEGROUP FileStreamGroup1 CONTAINS FILESTREAM ALTER DATABASE AdventureWorks ADD FILE ( NAME = N'AdventureWorks_media', FILENAME = N'E:\sqldata\AdventureWorks_media') TO FILEGROUP [FileStreamGroup1] GO

Ce fichier cre en ralit un rpertoire son emplacement, comme nous le voyons sur la figure 4.11.

Figure 4.11 Rpertoire des fichiers FILESTREAM

Puis, vous crez une table contenant une colonne de type FILESTREAM :
CREATE TABLE dbo.document ( documentId uniqueidentifier NOT NULL ROWGUIDCOL DEFAULT (NEWID()) PRIMARY KEY NONCLUSTERED , nom varchar(1000) NOT NULL, document varbinary(max) FILESTREAM);

Le GUID (uniqueidentifier ROWGUIDCOL) est ncessaire pour la gestion du FILESTREAM par le moteur de stockage. Terminons par un exemple de code, qui vous montre linsertion, et lextraction partir du FILESTREAM. Remarquez la mthode PathName() :
INSERT INTO dbo.document (nom, document)

4.2 Partitionnement

77

SELECT 'babaluga', CAST('plein de choses dire' as varbinary(max)) SELECT nom, CAST(document as varchar(max)) as document, document.PathName() FROM dbo.document

Volume de donnes Attention au nombre de fichiers stocks dans les rpertoires FILESTREAM. partir de quelques centaines de milliers de fichiers, des lenteurs srieuses peuvent se faire sentir. La source du problme peut tre le scan du rpertoire chaque ajout de fichier, pour gnrer un nom en format 8.3 li. Vous pouvez dsactiver ce comportement dans NTFS laide de lexcutable fsutil : fsutil behavior set disable8dot3 1 Vous devez redmarrer la machine pour que ceci soit pris en compte.

4.2 PARTITIONNEMENT
Mathmatiquement, laugmentation de la taille dune base de donnes entrane une diminution de ses performances en lecture et en criture. Grce la grande qualit des techniques doptimisation mises en uvre dans les SGBDR modernes, et condition que votre modle de donnes soit intelligemment construit, cette augmentation de temps de rponse nest en rien linaire. SQL Server peut retrouver avec une grande vlocit quelques lignes dans une table qui en compte des millions. Toutefois, il ny a pas de miracle : augmentation de taille signifie multiplication des pages de donnes et dindex, donc plus de lectures et plus de temps pour parcourir les objets. Pour pallier cette inflation de taille, vous pouvez partitionner vos tables. Le partitionnement consiste sparer physiquement des structures, soit pour en parallliser le traitement, soit pour retirer des lectures une partie des donnes moins souvent sollicite. Le partitionnement peut soit porter sur des lignes (partitionnement horizontal), soit sur des colonnes (partitionnement vertical). Le partitionnement horizontal est utile dans le cas de tables comportant un attribut temporel, par exemple une table de journalisation, de mouvements comptables, de factures, dactes ou doprations diverses. Les critures portent trs souvent sur des dates rcentes, et la grande majorit des lectures concerne une priode limite, par exemple les quelques dernires annes. En SQL Server 2000, nous traitions ce problme en crant des tables supplmentaires qui dupliquaient la structure de la table originale, par exemple laide dun ordre SELECT INTO, qui permet de crer une table et dy insrer le rsultat dun SELECT la vole. Il est tout fait possible de continuer utiliser cette technique dans SQL Server 2005/2008. Voici un

78

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

exemple qui utilise la structure de contrle TRY CATCH qui nexiste que depuis la version 20051 :
BEGIN TRANSACTION BEGIN TRY SELECT * INTO Sales.SalesOrderHeader_Archive2002 FROM Sales.SalesOrderHeader WHERE YEAR(OrderDate) = 2002 DELETE FROM Sales.SalesOrderHeader WHERE YEAR(OrderDate) = 2002 COMMIT TRANSACTION END TRY BEGIN CATCH ROLLBACK TRANSACTION END CATCH

Vous pouvez simplifier cet exemple partir de 2005 laide de linstruction DELETE OUTPUT. La lecture des lignes nest ainsi effectue quune seule fois :
BEGIN TRANSACTION BEGIN TRY SELECT 0 as SalesOrderID, RevisionNumber, OrderDate, DueDate, ShipDate INTO Sales.SalesOrderHeader_Archive2002 FROM Sales.SalesOrderHeader WHERE 1 = 22 DELETE FROM Sales.SalesOrderHeader OUTPUT DELETED.SalesOrderID, DELETED.RevisionNumber, DELETED.OrderDate, DELETED.DueDate, DELETED.ShipDate INTO Sales.SalesOrderHeader_Archive2002 WHERE YEAR(OrderDate) = 2002 COMMIT TRANSACTION END TRY BEGIN CATCH ROLLBACK TRANSACTION END CATCH

Que faire de cette nouvelle table darchive ? Vous pouvez modifier vos applications clientes pour ajouter par exemple un bouton de recherche sur les archives, qui
1. Lutilisation dune fonction dans le critre de recherche empche lutilisation ventuelle dun index. Nous le verrons dans la partie traitant de loptimisation des requtes. Dans ce cas, cest peu problmatique, dans la mesure o un scan de la table sera de toute manire effectu, le nombre de ligne retourner tant important. 2. Nous avons volontairement raccourci dans notre exemple le nombre de colonnes, car dans ce cas nous devions viter de copier la colonne SalesOrderID comme colonne de type IDENTITY (auto-incrmentale). LOUTPUT refusant dinsrer une valeur explicite dans un IDENTITY.

4.2 Partitionnement

79

lance une requte SELECT sur cette table au lieu de la table originelle. Mauvaise solution. La bonne rponse est : crez une vue, qui agrge les diffrentes tables par des UNION ALL, et effectuez la requte sur cette vue :
CREATE VIEW Sales.vSalesOrderHeaderWithArchives AS SELECT SalesOrderID, RevisionNumber, OrderDate, DueDate, ShipDate, 0 as Source FROM Sales.SalesOrderHeader UNION ALL SELECT SalesOrderID, RevisionNumber, OrderDate, DueDate, ShipDate, 2002 as Source FROM Sales.SalesOrderHeader_Archive2002

Ici, pour dterminer dans nos requtes de quelle table source provient chaque ligne, nous ajoutons une colonne gnre dans la vue. Vous pouvez ensuite modifier votre requte pour effectuer la recherche sur cette vue, ou prvoir deux types de recherche : une recherche sur la table originelle, et une recherche sur toute ltendue des donnes, en prvenant vos utilisateurs que la recherche simple est plus rapide, mais ne comporte que des valeurs rcentes. La recherche simple sera-t-elle plus rapide ? Pas forcment. Si nous lui en donnons les moyens, loptimiseur de SQL Server est capable de savoir si la requte que nous lanons sur cette vue concerne bien toutes les tables, ou si laccs une des tables suffit. Comment cela ? En plaant une contrainte CHECK sur la structure des tables sous-jacentes. Pour illustrer la diffrence que va amener cet ajout, commenons par analyser les tables affectes par cet ordre SQL :
SET STATISTICS IO ON GO SELECT * FROM Sales.vSalesOrderHeaderWithArchives WHERE OrderDate BETWEEN '20020301' AND '20020401';

Nous voulons les commandes passes en mars 2002... Rsultat des statistiques IO :
(305 row(s) affected) Table 'SalesOrderHeader_Archive2002'. Scan count 1, logical reads 18... Table 'SalesOrderHeader'. Scan count 1, logical reads 700...

Donc, 18 pages lues pour SalesOrderHeader_Archive2002, 700 pages lues pour SalesOrderHeader. Nous avons cr la table Sales.SalesOrderHeader_Archive2002 pour quelle ne contienne que les lignes dont lOrderDate est compris dans lanne 2002. La table Sales.SalesOrderHeader ne contient donc plus que des lignes dont lOrderDate est diffrent de 2002. Exprimons cela en contraintes CHECK sur la colonne OrderDate :
ALTER TABLE Sales.SalesOrderHeader

80

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

WITH CHECK ADD CONSTRAINT chk$SalesOrderHeader$OrderDate CHECK (OrderDate NOT BETWEEN '20020101' AND '20021231 23:59:59.997'); GO ALTER TABLE Sales.SalesOrderHeader_Archive2002 WITH CHECK ADD CONSTRAINT chk$SalesOrderHeader_Archive2002$OrderDate CHECK (OrderDate BETWEEN '20020101' AND '20021231 23:59:59.997');

Le WITH CHECK nest pas ncessaire, car la vrification de la contrainte sur les lignes existantes est effectue par dfaut. Nous dsirons simplement mettre laccent sur le fait que la contrainte doit tre reconnue comme tant applique aux lignes existantes, pour que loptimiseur puisse sen servir. Relanons le mme ordre SELECT, et observons les statistiques de lectures :
Table 'SalesOrderHeader_Archive2002'. Scan count 1, logical reads 18...

Seule la table SalesOrderHeader_Archive2002 a t affecte. Grce la contrainte spcifie, loptimiseur sait avec certitude quil ne trouvera aucune ligne dans Sales.SalesOrderHeader qui corresponde la fourchette de dates demande dans la clause WHERE. Il peut donc liminer cette table de son plan dexcution.

En utilisant les contraintes CHECK, vous pouvez manuellement partitionner horizontalement vos tables, les rejoindre dans une vue, et ainsi augmenter vos performances de lecture sans sacrifier les fonctionnalits.

Cette technique conserve un dsavantage : ladministration (cration de tables darchive) reste manuelle. partir de SQL Server 2005, des fonctions intgres de partitionnement horizontal de table et dindex simplifient lapproche.

Partitionnement intgr
Les fonctionnalits de partitionnement sont disponibles sur SQL Server 2005 dans ldition Entreprise seulement. Tout ce que nous venons de faire manuellement, et plus encore, est pris en charge par le moteur de stockage. Depuis SQL Server 2005, une couche a t ajoute dans la structure physique des donnes, entre lobjet et ses units dallocation : la partition. Lobjectif premier du partitionnement est de placer diffrentes parties de la mme table sur diffrentes partitions de disque, afin de parallliser les lectures physiques et de diminuer le nombre de pages affectes par un scan ou par une recherche dindex. La lecture en parallle sur plusieurs units de disques nest vraiment intressante, bien entendu, que lorsque les partitions sont situes sur des disques physiques diffrents, et de prfrence sur des bus de donnes diffrents, ou sur une baie de disques ou un SAN, sur lequel on a soigneusement organis les partitions. Chaque disque physique nayant quune seule tte de lecture, il ne sert rien de compter sur un simple partitionnement logique sur le mme disque : les accs ne pourront se faire quen

4.2 Partitionnement

81

srie. De plus, lintrt ne sera rel que pour des VLDB (bases de donnes de grande taille). En effet, dans les bases de donnes de taille moyenne, les lectures sur le disque devraient tre autant que possible vites par une quantit suffisante de RAM. La plupart des pages sollicites sont alors dj contenues dans le buffer et tout accs aux disques est vit. Mais mme dans la RAM, il peut tre intressant de nattaquer quune partie de la table. Un autre intrt du partitionnement est la simplicit et la rapidit avec laquelle on peut ajouter ou retirer des partitions de la table. Nous allons le voir en pratique. Le partitionnement se prpare avant la cration de la table : il ne peut se faire sur une table existante. Pour appliquer un partitionnement des donnes dj prsentes, il faut les faire migrer vers une nouvelle table, partitionne. De plus, lopration ne peut se faire en SQL Server 2005 que par code T-SQL. SQL Server 2008 propose un assistant (clic droit sur la table dans lexplorateur dobjet, menu Storage partir de la CTP 6 de fvrier 2008), mais pour rester compatible avec les deux versions, nous vous prsentons ici les commandes T-SQL. Pour partitionner, vous devez suivre les tapes suivantes dans lordre : Crer autant de groupes de fichiers que vous dsirer crer de partitions. Crer une fonction de partitionnement (partition function). Elle indique sur quel type de donnes et pour quelles plages de valeurs, les partitions vont tre cres. Crer un plan de partitionnement (partition scheme) (traduit dans laide en franais par schma de partitionnement). Il attribue chaque partition dclare dans la fonction, un groupe de fichiers. Crer une table qui se repose sur le plan de partitionnement. Le partitionnement consiste placer chaque partition sur son propre groupe de fichiers. Vous devez donc pralablement crer tous vos groupes de fichiers. Si vous cherchez partitionner une trs grande table pour optimiser les lectures physiques, faites au moins en sorte que les fichiers dans lesquels vous allez placer les lignes le plus souvent requtes se trouvent sur des disques physiques diffrents.
USE Master; ALTER DATABASE AdventureWorks ADD FILEGROUP fg1; ALTER DATABASE AdventureWorks ADD FILEGROUP fg2; ALTER DATABASE AdventureWorks ADD FILEGROUP fg3; GO ALTER DATABASE AdventureWorks ADD FILE ( NAME = data1, FILENAME = 'c:\temp\AdventureWorksd1.ndf', SIZE = 1MB, maxsize = 100MB, FILEGROWTH = 1MB) TO FILEGROUP fg1; ALTER DATABASE AdventureWorks

82

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

ADD FILE ( NAME = data2, FILENAME = 'c:\temp\AdventureWorksd2.ndf', SIZE = 1MB, maxsize = 100MB, FILEGROWTH = 1MB) TO FILEGROUP fg2; ALTER DATABASE AdventureWorks ADD FILE ( NAME = data3, FILENAME = 'c:\temp\AdventureWorksd3.ndf', SIZE = 1MB, maxsize = 100MB, FILEGROWTH = 1MB) TO FILEGROUP fg3;

Ceci tant fait, crons une fonction de partitionnement pour notre table SalesOrderHeader :
USE AdventureWorks; CREATE PARTITION FUNCTION pfOrderDate (datetime) AS RANGE RIGHT FOR VALUES ('20020101', '20030101');

Nous passons en paramtre le type de donnes auquel sapplique la fonction de partitionnement, nous indiquons aussi si les plages vont tre cres pour des valeurs situes gauche ou droite des frontires exprimes. Ici, par exemple, la dernire partition va contenir tous les OrderDate plus grands que le 01/01/2003, cest--dire droite de cette valeur. Cette fonction dclare donc trois partitions : tout ce qui vient avant la premire valeur, tout ce qui est entre '20020101' et '20030101', puis tout ce qui est droite de '20030101'. Ce que nous pouvons vrifier grce aux vues de mtadonnes :
SELECT pf.name, pf.type_desc, pf.boundary_value_on_right, rv.boundary_id, rv.value, pf.create_date FROM sys.partition_functions pf JOIN sys.partition_range_values rv ON pf.function_id = rv.function_id ORDER BY rv.value;

Pour attribuer les partitions aux groupes de fichiers, nous crons un plan de partitionnement :
CREATE PARTITION SCHEME psOrderDate AS PARTITION pfOrderDate TO (fg1, fg2, fg3);

Il nous reste ensuite crer la table. Il suffit de rcuprer le script de cration de la table Sales.SalesOrderHeader et de le modifier lgrement :
CREATE TABLE [Sales].[SalesOrderHeader_Partitionne]( [SalesOrderID] [int] IDENTITY(1,1) NOT NULL, --... [ModifiedDate] [datetime] NOT NULL DEFAULT (getdate()) ) ON psOrderDate(OrderDate);

4.2 Partitionnement

83

Comme vous le voyez, au lieu de crer la table sur un groupe de fichiers, nous la crons sur le plan de partitionnement. Nous donnons en paramtre la colonne sur laquelle le partitionnement aura lieu, ce qui porte le nom de cl de partitionnement. Cette cl ne peut tre composite : elle doit tre contenue dans une seule colonne. Pour que cet exemple fonctionne, nous devons tricher et supprimer la cl primaire, car nous ne pouvons pas aligner notre index unique sur le partitionnement de la table. Nous reviendrons sur cette problmatique quand nous aborderons le sujet des index au chapitre 6. Sachez simplement que les index peuvent tre aussi partitionns. Par dfaut, les index suivent le mme plan de partitionnement que la table, ils sont dits aligns . Mais ils peuvent aussi tre partitionns diffremment, ou ntre pas partitionns et rsider intgralement sur un seul groupe de fichiers. Ce choix explicite doit tre indiqu dans linstruction de cration de lindex, comme nous le voyons dans le code prcdent. Alimentons ensuite la table :
INSERT INTO Sales.SalesOrderHeader_Partitionne ( RevisionNumber, OrderDate, DueDate, ShipDate, Status, OnlineOrderFlag, PurchaseOrderNumber, AccountNumber, CustomerID, ContactID, SalesPersonID, TerritoryID, BillToAddressID, ShipToAddressID, ShipMethodID, CreditCardID, CreditCardApprovalCode, CurrencyRateID, SubTotal, TaxAmt, Freight, Comment, rowguid, ModifiedDate ) SELECT RevisionNumber, OrderDate, DueDate, ShipDate, Status, OnlineOrderFlag, PurchaseOrderNumber, AccountNumber, CustomerID, ContactID, SalesPersonID, TerritoryID, BillToAddressID, ShipToAddressID, ShipMethodID, CreditCardID, CreditCardApprovalCode, CurrencyRateID, SubTotal, TaxAmt, Freight, Comment, rowguid, ModifiedDate FROM Sales.SalesOrderHeader;

Nous pouvons voir ensuite comment les lignes ont t attribues aux partitions par les vues systme dallocation :
SELECT object_name(object_id) AS Name, partition_id, partition_number, rows, allocation_unit_id, type_desc, total_pages FROM sys.partitions p JOIN sys.allocation_units a ON p.partition_id = a.container_id WHERE object_id=object_id('Sales.SalesOrderHeader_Partitionne') ORDER BY partition_number;

Cette requte est dutilit publique, et peut tre utilise pour toute table, mme non partitionne. Elle permet de connatre les attributions de pages selon les dif-

84

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

frents types (type_desc) : IN ROW DATA Ligne lintrieur de la page ; LOB DATA objet large ; ROW OVERFLOW DATA Ligne partage sur plusieurs pages (voir section 4.1.2).

Vous pouvez galement employer directement dans la table une fonction qui renvoie, dans quelle partition doit se trouver chaque ligne (selon la fonction de partitionnement. Elle ne va pas vrifier physiquement si la ligne se trouve dans le bon groupe de fichier). Cela permet de tester le respect des plages :
SELECT COUNT(*) as cnt, MAX(OrderDate) as MaxDate, MIN(OrderDate) as MinDate, $PARTITION.pfOrderDate(OrderDate) AS Partition FROM Sales.SalesOrderHeader_Partitionne GROUP BY $PARTITION.pfOrderDate(OrderDate) ORDER BY Partition;

Le partitionnement permet des facilits dadministration non ngligeables. Ajouter, supprimer, dplacer, sparer ou fusionner des partitions peut tre pris en charge trs rapidement laide des instructions ddies SWITCH, MERGE et SPLIT. Il nentre pas dans le cadre de cet ouvrage de vous prsenter en dtail leur utilisation. Voici pour exemple comment dplacer une partition sur une autre table (existante) :
ALTER TABLE Sales.SalesOrderHeader_Partitionne SWITCH PARTITION 1 TO Sales.SalesOrderHeader_Old;

Ces oprations sont pratiquement instantanes, car elles agissent directement au niveau du stockage de la partition, et non en dplaant les lignes dune table une autre. Elles permettent ainsi une administration extrmement efficace, par exemple pour un archivage rgulier.

Attention La cration dindex non aligns empche lutilisation de ces instructions. On ne peut pas dplacer une partition si un index non align existe sur la table. Dans notre exemple de partitionnement, si nous avions cr une cl primaire sur la colonne SalesOrderID, nous aurions d la faire nonclustered et non-aligne, comme ceci : CONSTRAINT [PK_SalesOrderHeader_Partitionne_SalesOrderID] PRIMARY KEY NONCLUSTERED (SalesOrderID) ON [PRIMARY]. Cela nous aurait empchs de raliser des SWITCH aprs coup.

Enfin, grce la possibilit de raliser des sauvegardes de fichiers ou de groupes de fichiers, la partitionnement de table permet doptimiser vos stratgies de backup.

4.3 tempdb

85

4.3 TEMPDB
tempdb est la base de donnes systme qui recueille tous les objets temporaires crs dans SQL Server. Elle est dtruite et reconstruite chaque dmarrage de linstance SQL, partir de la base de donnes model, tout comme les bases utilisateurs. Si vous voulez changer une option de tempdb, ou sa taille initiale, vous pouvez modifier ces options dans model. tempdb est en mode de rcupration simple. Cela ne peut tre chang, et il ny a aucune raison de vouloir mettre tempdb dans un mode complet. tempdb contient non seulement les objets temporaires (tables, procdures, cls de chiffrement, etc.) crs avec les prfixes # ou ##, et les variables de type table, mais vous pouvez galement y crer des objets persistants , comme dans nimporte quelle base de donnes. Ces objets y seront conservs jusqu leur suppression manuelle par un DROP, ou jusquau prochain redmarrage de linstance. Outre les objets temporaires, tempdb recueille aussi deux autres types de structures : des dpts de version (version stores) pour des lignes impliques dans du suivi de version (row versioning, voir section 4.2.1), et des objets internes (internal objects). Le row versioning est utilis pour quelques fonctionnalits comme le niveau disolation SNAPSHOT, les pseudo-tables de dclencheurs, MARS (Multiple Active Result Sets), la gnration dindex ONLINE et certaines commandes DBCC comme CHECKDB. Les objets internes permettent de stocker des donnes intermdiaires pour tous types doprations SQL Server (plans de requte, variables LOB ou XML, rsultats de curseurs, messages Service Broker en transit, etc.). Les objets internes et les dpts de versions ne sont pas inscrits dans le journal de transaction de tempdb. Ils sont optimiss autant que faire se peut.
Vous trouverez plus de dtails sur leur stockage interne dans le livre blanc Working with tempdb in SQL Server 2005 (http://www.microsoft.com/technet/ prodtechnol/sql/2005/workingwithtempdb.mspx).

Les objets utilisateurs (tables temporaires, fonctions table et variables de type table) gnrent, eux, des critures dans le journal de transactions. Au fil des versions de SQL Server, tempdb apporte de plus en plus doptimisations, dans la faon de grer la journalisation des transactions, lallocation et la libration des objets. Cest un problme critique, car tempdb subit une grande quantit de crations et destructions dobjets, contrairement une base de donnes utilisateur, qui ne subit en utilisation normale que trs peu de modifications dobjets. Ainsi, lorsque tempdb est trs sollicit, cause par exemple dune forte utilisation de tables temporaires dans le code SQL, il peut se produire une contention sur les structures du fichier de donnes de tempdb (due aux allocations et librations de pages et dextensions pour grer le contenu), ou sur les tables de mtadonnes (due un grand nombre de crations ou de suppressions dobjets). Ce risque de contention est diminu par diffrentes mthodes (diminution du verrouillage des structures de donnes, ges-

86

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

tion en cache des tables). partir de SQL Server 2005, les variables de type table et les tables temporaires locales de petite taille sont gardes en cache mmoire, et pour les tables de moins de 8 Mo, la page dallocation (IAM) et une page de donnes sont conserves en mmoire au cas o la table serait recre.

Attention Certaines pratiques empchent SQL Server de cacher les tables temporaires. La modification de sa structure aprs cration par exemple, ou si elles sont utilises dans du code SQL dynamique, ou un batch de requtes plutt que dans une procdure stocke.

Si malgr cela, une contention se produit dans tempdb, les performances peuvent sen retrouver srieusement affectes. Cest une situation rare, rencontre sur des serveurs trs sollicits. Pour dtecter une contention, vous pouvez surveiller les attentes laide de la vue de gestion dynamique sys.dm os waiting tasks, qui liste les tches en attentes. La contention des structures de donnes se traduit sur des attentes de libration de latches sur des pages de PFS ou de SGAM. Les latches sont des verrous lgers de protection daccs physique (voir section 7.2.1).
SELECT session_id, wait_duration_ms, resource_description FROM sys.dm_os_waiting_tasks WHERE wait_type like 'PAGE%LATCH_%' AND resource_description like '2:%'

Des compteurs du moniteur de performances donnent aussi des indications sur les latches en gnral. Ils se trouvent dans lobjet de compteurs SQL:Latches (reportezvous la section 5.3).
Vous pouvez lire ce document pour obtenir plus de dtails sur loptimisation de tempdb sur http://www.microsoft.com/technet/prodtechnol/sql/2005/workingwithtempdb.mspx

Drapeau de trace 1118 Le document mentionn prcdemment voque le drapeau de trace 1118, qui plac globalement, change la mthode dallocation de nouveaux objets dans tempdb pour viter la contention sur les SGAM. Ce drapeau de trace est dusage dlicat, et peut tre contre performant. Rfrezvous cet article de blog pour une analyse raisonne : http://sqlblog.com/blogs/linchi_shea/ archive/2007/08/10/reduce-the-contention-on-tempdb-with-trace-flag-1118-take-2.aspx

Taille et utilisation de tempdb


Les objets qui peuvent prendre de la place dans tempdb sont des objets utilisateur volumineux, comme des tables temporaires. Les tables temporaires sont supprimes la fin de la session qui les a cres. Si une session reste ouverte, et quune table temporaire na pas t explicitement supprime laide dun DROP, elle peut occuper de lespace un certain temps. Cest donc une habitude importante prendre, de supprimer une table temporaire lorsquon nen a plus besoin. De mme, si la table

4.3 tempdb

87

temporaire est enrle dans une transaction, le journal de transactions de tempdb ne pourra se tronquer que lorsque la transaction sera termine. Les dpts de versions peuvent aussi occuper une place importante dans tempdb. Ils contiennent chaque ligne implique dans un row versioning. Il existe uniquement deux dpts de versions dans tempdb : lun est ddi aux oprations sur index ONLINE, et lautre toutes les utilisations de row versioning. Nous verrons dans le chapitre sur les index ce quimpliquent les oprations dindex ONLINE, sachez simplement que plus la cration de lindex en mode ONLINE prendra de temps, plus le dpt de versions est susceptible de grandir. Lautre dpt de versions est susceptible de grandir avec le nombre doprations impliquant du row versioning, et dans le cas o une transaction est maintenue, qui utilise une fonctionnalit de row versioning. La vue de gestion dynamique sys.dm tran active snapshot database transactions permet dobtenir la liste toutes les transactions qui gnrent, ou peuvent potentiellement accder des versions de lignes. Elle peut vous servir dtecter des transactions longues qui maintiennent des versions :
SELECT * FROM sys.dm_tran_active_snapshot_database_transactions ORDER BY elapsed_time_seconds DESC;

La vue de gestion dynamique sys.dm_db_file_space_usage, affiche lutilisation des fichiers de donnes. Comme son nom ne lindique pas, elle sapplique uniquement tempdb. Donc, quel que soit le contexte de base dans lequel vous lexcutez, elle affichera les informations de tempdb. Elle donne le nombre de pages ddies aux dpts de versions (version store reserved page count), aux objets internes (internal object reserved page count) et aux objets utilisateur (user object reserved page count), par fichier de donnes. Pour obtenir le volume en octets, il vous suffit de multiplier ces nombres par 8192. Exemple :
SELECT SUM(user_object_reserved_page_count)*8 as user_object_kb, SUM(internal_object_reserved_page_count)*8 as internal_object_kb, SUM(version_store_reserved_page_count)*8 as version_store_kb FROM sys.dm_db_file_space_usage; -- ou en Mo SELECT SUM(user_object_reserved_page_count)*1.0/128 as user_object_mb, SUM(internal_object_reserved_page_count)*1.0/128 as internal_object_mb, SUM(version_store_reserved_page_count)*1.0/128 as version_store_mb FROM sys.dm_db_file_space_usage;

La vue sys.dm db session space usage est intressante car elle indique le nombre dallocations et de dallocations dobjets internes et dobjets utilisateur par session. Si vous souhaitez connatre limpact dune procdure ou dun batch de requtes sur tempdb, excutez le code voulu, puis, dans la mme session, lancez une requte comme celle-ci :
SELECT * FROM sys.dm_db_session_space_usage WHERE session_id = @@SPID;

88

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

La vue sys.dm db task space usage donne la mme information par tche en activit. Elle peut tre jointe sys.dm exec requests pour trouver le plan dexcution :
SELECT tsu.*, DB_NAME(er.database_id) as db, er.cpu_time, er.reads, er.writes, er.row_count, eqp.query_plan FROM sys.dm_db_task_space_usage tsu JOIN sys.dm_exec_requests er ON tsu.session_id = er.session_id AND tsu.request_id = er.request_id CROSS APPLY sys.dm_exec_query_plan(er.plan_handle) eqp;

Plus dinformations avec des exemples de supervision vous sont donnes dans les BOL, sous lentre Rsolution des problmes despace disque insuffisant dans tempdb (Troubleshooting Insufficient Disk Space in tempdb).

Modification de lemplacement de tempdb


Vous pouvez modifier lemplacement des fichiers de donnes et de journal de tempdb. Comme la base est recre chaque dmarrage, vous devez appliquer les commandes suivantes, puis redmarrer votre instance pour que le changement soit effectif.
ALTER DATABASE tempdb MODIFY FILE (name = tempdev, filename = 'E:\Sqldata\tempdb.mdf') GO ALTER DATABASE tempdb MODIFY FILE (name = templog, filename = 'E:\Sqldata\templog.ldf') GO

Aprs redmarrage du service, vous pouvez excuter sp_helpfile tempdb ou SELECT * FROM tempdb.sys.database_files pour vrifier que le changement est effectif.

4.4 CONTRLE DE LATTRIBUTION DES RESSOURCES


Gouverneur de requtes
SQL Server intgre un contrleur de cot de requte, qui permet dviter lexcution dinstructions qui sont avant leur excution estimes lourdes. Il est nomm gouverneur de requtes (query governor). Vous le configurez pour toute linstance :
exec sp_configure 'show advanced options', 1 reconfigure exec sp_configure 'query governor cost limit', 10 reconfigure exec sp_configure 'show advanced options', 0 reconfigure

4.4 Contrle de lattribution des ressources

89

ou pour une session :


SET QUERY_GOVERNOR_COST_LIMIT 10;

Le nombre pass en paramtre correspond au nombre de secondes estimes par le moteur SQL, par rapport une configuration de machine de test de Microsoft : il ne sagit pas de vraies secondes de votre serveur. Lestimation elle-mme ne compte bien entendu pas les attentes diverses auxquelles lexcution peut tre soumise. Il sagit donc dune estimation trs estime, mais cela peut offrir un premier niveau de contrle de charge de votre serveur. Si une instruction est demande, dont lestimation dpasse ce nombre, la session recevra en retour le message derreur suivant, et lexcution sera annule avant mme de commencer.
Msg 8649, Level 17, State 1... The query has been canceled because the estimated cost of this query (1090057) exceeds the configured threshold of 10. Contact the system administrator.

Au programme client de rcuprer lerreur...

SQL Server 2008 : le gouverneur de ressources SQL Server 2008 offre un outil dattribution de ressources systme (CPU et mmoire), par session, nomm le gouverneur de ressources (resource governor). Il permet de configurer ces ressources en pool (resource pools), qui indiquent quelles sont les limites de ressources attribuables un groupe de charges de travail. Deux pools systme existent, le pool par dfaut (default), et le pool interne (internal). Vous pouvez crer vos propres pools afin de limiter des charges de travail. La figure 4.12 prsente linterface de cration de pools.

Figure 4.12 Configurer le gouverneur de ressources

90

Chapitre 4. Optimisation des objets et de la structure de la base de donnes

Nous pouvons aussi crer un pool par code :


CREATE RESOURCE POOL [intrus] WITH( min_cpu_percent=0, max_cpu_percent=20, min_memory_percent=0, max_memory_percent=10)

Lors de chaque modification du gouverneur de ressources, vous devez activer la modification :


ALTER RESOURCE GOVERNOR RECONFIGURE;

Ainsi, le gouverneur de ressources est activ. Si vous souhaitez le dsactiver (les sessions ne seront ainsi plus contrles) :
ALTER RESOURCE GOVERNOR DISABLE;

Un ALTER RESOURCE GOVERNOR RECONFIGURE le ractive. Les ressources sont partages entre les pools au prorata des valeurs indiques, selon un algorithme dcrit dans lentre des BOL 2008 Concepts du gouverneur de ressources (Resource Governor Concepts). Dans les pools sont placs des groupes de charges de travail. Ces groupes rassemblent simplement des instructions SQL lances sur le serveur, qui sont attribues ce groupe automatiquement selon des rgles de classification. Voici un exemple de cration de groupe :
CREATE WORKLOAD GROUP [mchantes requtes] WITH( group_max_requests=10, importance=Low, request_max_cpu_time_sec=0, request_max_memory_grant_percent=25, request_memory_grant_timeout_sec=0, max_dop=1) USING [intrus] GO ALTER RESOURCE GOVERNOR RECONFIGURE;

Comme vous le voyez, vous pouvez dfinir limportance du groupe et le degr de paralllisme (max_dop). Vous crez ensuite une fonction de classification, que le gouverneur de ressources va appeler pour attribuer chaque nouvelle ouverture de session un groupe. Vous ne pouvez crer quune seule fonction, qui effectuera le routage global. En labsence de fonction de classification, toutes les sessions utilisateurs seront attribues au groupe default. Lorsque la fonction est crite, vous la dclarez au gouverneur de ressources, comme dans lexemple suivant :
CREATE FUNCTION dbo.rgclassifier_v01() RETURNS SYSNAME WITH SCHEMABINDING AS BEGIN DECLARE @grp_name AS SYSNAME

4.4 Contrle de lattribution des ressources

91

IF (SUSER_NAME() = 'Nicolas') or (APP_NAME() LIKE '%Elysee%') SET @grp_name = 'mchantes requtes' ELSE SET @grp_name = NULL RETURN @grp_name END GO -- Register the classifier function with Resource Governor ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION= dbo.rgclassifier_v01) GO ALTER RESOURCE GOVERNOR RECONFIGURE GO

Lorsque la fonction retourne NULL, la session est attribue au groupe par dfaut. Lappartenance des sessions au groupe peut sobtenir ainsi :
SELECT * FROM sys.dm_exec_sessions es JOIN sys.resource_governor_workload_groups rswg ON es.group_id = rswg.group_id;

Vous pouvez dplacer dynamiquement un groupe dun pool un autre sans perturber les sessions actives :
ALTER WORKLOAD GROUP [mchantes requtes] USING [default]; GO ALTER RESOURCE GOVERNOR RECONFIGURE GO

Vous disposez de compteurs de performances indiquant ltat du gouverneur de ressources (entre BOL 2008 Resource Governor Monitoring ), ainsi que des vues de mtadonnes et de gestion dynamique (entre BOL 2008 Resource Governor DDL and System Views ).

5
Analyse des performances

Objectif
Vous ne pouvez agir sans savoir. Lamlioration de performance doit se fonder sur les faits, et les amliorations doivent tre vrifiables ensuite. Les outils de surveillance et de suivi vous donnent les lments indispensables la comprhension du problme, donc sa rsolution. Ce chapitre prsente les outils danalyse votre disposition.

5.1 SQL SERVER MANAGEMENT STUDIO


SSMS (SQL Server Management Studio) propose plusieurs outils pour analyser la performance de vos requtes SQL. Un plan dexcution graphique est affichable, nous en parlerons en dtail section 8.1. Nous prsentons ici les autres options. Les statistiques du client sont des chiffres collects par la bibliothque daccs aux donnes, et qui sont visibles en tableau dans SSMS. Elles vous permettent de vous faire une ide des instructions excutes, du nombre de transactions, et surtout du volume de donnes qui a transit jusqu vous pour un batch de requtes. Lorsque vous activez cette fonctionnalit, laide du bouton de la barre doutils illustr sur la figure 5.1, vous obtenez un nouvel onglet de rsultat.

94

Chapitre 5. Analyse des performances

Figure 5.1 Afficher les statistiques clients dans SSMS

Si vous excutez le mme batch plusieurs fois, vous obtenez un tableau comparatif des statistiques dexcution, avec des indicateurs flchs vous indiquant la tendance par rapport lexcution prcdente (figure 5.2).

Figure 5.2 Rsultat des statistiques clients dans SSMS

Vous pouvez rinitialiser ces statistiques laide de la commande Reset Client Statistics du menu Query. Loption de session SET STATISTICS TIME ON retourne les statistiques de temps dexcution, visibles dans longlet Messages . Voici un exemple de retour :
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 1 ms.

5.1 SQL Server Management Studio

95

SQL Server Execution Times: CPU time = 16 ms, elapsed time = 22 ms.

Les statistiques de temps ne sont pas des mesures prcises : elles changent tout moment, selon la charge du serveur, du rseau et les verrous. Nanmoins cela vous donne une ide du temps de compilation, et la diffrence entre le temps CPU et le temps total (elapsed) vous indique des attentes sur les verrous, les entres/sorties et le rseau. Loption de session SET STATISTICS IO ON retourne les statistiques dentres/sorties fournies par le moteur SQL Server, en unit de pages. Elles sont une mesure plus prcise, car elles indiquent de faon constante le vrai cot dune requte pour le moteur de stockage. Exemple de retour :
Table 'Contact'. Scan count 1, logical reads 1276, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Vous voyez le nombre de scan de table ou dindex (mal traduit dans la version franaise par analyse ), le nombre de reads logiques, de reads physiques (les appels aux pages non trouves dans le buffer), les lectures anticipes (SQL Server peut appeler lavance des pages quil prvoit dutiliser plus tard dans lexcution de la requte), et les lectures de pages LOB (objets larges et row overflow).

Les rsultats de SET STATISTICS IO ON, comme le texte des messages derreur, sont rcuprables par trace, dans lvnement Errors and Warnings : User Error Messages.

Vous pouvez activer ces options pour toutes les sessions en les activant automatiquement dans les options SSMS (figure 5.3).

96

Chapitre 5. Analyse des performances

Figure 5.3 Options de la session SSMS

5.2 SQL TRACE ET LE PROFILER


SQL Trace est une technologie intgre au moteur SQL Server, qui permet de fournir un client le dtail de quantit dvnements se produisant dans les diffrentes parties de SQL Server. SQL Trace peut tre compar un dbogueur. Devant un programme complexe, organis en multiples classes et modules, un dveloppeur qui doit identifier la cause dun problme partir dun rapport de bug na quune mthode efficace disposition : reproduire le problme en suivant pas pas le comportement du code laide de son outil de dbogage, observant les valeurs attribues aux variables, les changements dtat des classes, litration des boucles. Sans cette capacit entrer dans les oprations du code, observer finement ce qui se passe, le travail de dbogage serait rduit une recherche fastidieuse faite dessais, dinstructions PRINT, de lecture attentive de code. Un serveur SQL ajoute des niveaux de complexit supplmentaires, qui rendent la mthode exprimentale cauchemardesque : on peut rarement deviner la simple lecture dune requte quel en est le plan dexcution, cest-dire la stratgie que va appliquer SQL Server pour en assurer lexcution optimale. Pour le savoir, il faudrait faire soi-mme tout le travail de loptimiseur : inspecter les tables, lister les index et leurs statistiques, estimer le nombre de lignes impactes, essayer plusieurs stratgies, estimer leur cot Il est donc pratiquement impossible de deviner ce qui va prcisment se passer. De plus, lexcution concurrentielle des requtes rend les performances dpendantes du contexte : charge de travail des processeurs, attente sur des verrous, performance des tables temporaires

5.2 SQL Trace et le profiler

97

Sans SQL Trace, SQL Server serait une bote noire, nous naurions aucun moyen didentifier prcisment les raisons dun ralentissement soudain, ou dune charge excessive du systme. Dans chaque instance de SQL Server, un sous-systme nomm le contrleur de trace (trace controller) est responsable de la collecte dvnements produits par des fournisseurs propres chaque partie du moteur, et lenvoi de ces vnements aux clients inscrits. Ces fournisseurs ne sont activs que si au moins un client coute activement un des vnements quils produisent. Le contrleur de trace distribue ensuite les rsultats des fournisseurs dentres/sorties (trace I/O providers), ce qui permet une trace dtre directement sauve dans un fichier binaire par le serveur, ou envoye un client, comme le profiler. Le profiler, incorrectement traduit dans certaines versions de SQL Server par gestionnaire de profils est donc en ralit un programme client qui affiche les informations dune trace SQL. Il est loutil incontournable de loptimisation. Il vous permet de suivre le comportement de votre serveur, en interceptant tous les vnements envoys par SQL Trace, dont bien entendu les requtes SQL, accompagns de prcieuses informations sur leur impact et leur excution. Il est important de bien le connatre et nous allons donc en dtailler les fonctionnalits. Le profiler peut afficher en temps rels un flux de trace SQL, mais aussi charger une trace enregistre pralablement dans un fichier de trace ou dans une table SQL. Il peut sauvegarder une trace dans ces deux destinations, et galement rejouer une trace sur un serveur SQL, cest--dire excuter nouveau toutes les requtes SQL contenues dans la trace, ce qui est fort utile pour faire un test de charge, par exemple. Vous en trouvez licne dans le menu de SQL Server, dossier Performances tools (outils de performance). Lorsque vous crez une nouvelle session, une fentre deux onglets permet de slectionner un certain nombre doptions.

98

Chapitre 5. Analyse des performances

Figure 5.4 Premier onglet du profiler

Dans longlet General, vous pouvez nommer votre trace, ce qui est utile lorsque vous en crez plusieurs (le profiler est une application MDI (Multiple Documents Interface) qui peut excuter plusieurs traces en mme temps), et pour prdfinir le nom du fichier lorsque vous sauvegarderez la trace. En gris, vous pouvez voir le nom et la version du fournisseur SQL Trace. Les profilers 2005 et 2008 peuvent lancer des traces sur des versions antrieures de SQL Server. Dans ce cas, les vnements et colonnes disposition refltent les possibilits de la version du serveur. Vous pouvez fonder votre nouvelle trace sur un modle (template), cest--dire un squelette de trace comprenant toutes les proprits choisies dans le profiler, y compris les choix dvnements, les colonnes et les filtres. Vous pouvez utiliser le choix de modles prdfini, mais aussi crer vos modles personnaliss en sauvant votre trace comme nouveau modle. Vous pouvez choisir de visualiser votre trace en temps rel, en laissant les vnements se drouler devant vos yeux, et vous pouvez aussi sauvegarder cette trace dans un fichier binaire ou XML, ou dans une table SQL Server, au fur et mesure de son excution en indiquant la destination dans cet onglet, ou ensuite, laide de la commande Save As du menu File.

Lcriture la vole dune trace volumineuse dans une table peut tre trs pnalisante pour le serveur SQL. Prfrez lenregistrement en fichier binaire. Vous aurez ensuite la possibilit de la recharger dans le profiler et de le sauvegarder dans

5.2 SQL Trace et le profiler

99

une table SQL au besoin. Si vous choisissez nanmoins dcrire directement dans une table, faites-le de prfrence sur un serveur diffrent de celui trac.

Vous pouvez indiquer une taille maximale du fichier de trace et segmenter vos fichiers de trace avec loption enable file rollover. Lorsque la taille maximale indique est atteinte, un nouveau fichier numrot est cr. Si votre fichier est nomm matrace.trc, les suivants seront matrace_1.trc, matrace_2.trc, et ainsi de suite. Si vous dcochez loption enable file rollover, lenregistrement sarrtera lorsque la taille sera atteinte. La visualisation en temps rel, elle, se poursuivra. Pensez laffichage dans le profiler et lenregistrement de la trace comme des redirections spares dune mme trace. Si vous choisissez un enregistrement dans une table, vous pouvez limiter le nombre de milliers de lignes y crire, avec loption Set maximum rows. Si votre objectif nest pas danalyser en temps rel lactivit de votre serveur, mais de laisser tourner votre trace pour, par exemple, tablir une baseline, vous pouvez indiquer un arrt automatique de la trace avec loption Enable trace stop time. Nous verrons quil est prfrable dans ce cas de lancer une trace directement sur le serveur. Longlet Events Selection est le cur du profiler : il permet de choisir les informations retournes : vnements, colonnes, et filtres. Les classes dvnements rcuprables sont groupes en catgories. Elles publient un certain nombre dinformations prsentes dans des colonnes. Toutes les colonnes ne sont pas alimentes pour chaque classe. Vous trouverez les colonnes utiles dans les BOL, sous lentre correspondant lvnement. Par dfaut, et pour simplifier laffichage, cet onglet ne vous montre que les vnements slectionns dans le modle choisi. Pour faire votre slection parmi tous les vnements disponibles, cochez Show all events. Vous obtenez ainsi une liste des catgories et des classes dvnements associs. Les catgories sont reproduites dans le tableau 5.1.
Tableau 5.1 Catgories dvnements de trace * Gnre une activit importante Catgorie Broker CLR Cursors Database Depreciation Errors and Warnings vnements Service Broker. Excution des objets .NET intgrs SQL Server. Oprations de curseurs T-SQL. Changement de taille des fichiers de base de donnes, et vnements de mirroring. Alertes dutilisation de fonctionnalits obsoltes. Avertissements et exceptions diverses. Description

100

Chapitre 5. Analyse des performances

Catgorie Full Text Locks OLEDB Objects Performance Progress Report Query Notifications Scans Security Audit Server Sessions Stored Procedures TSQL Transactions User Configurable Description Alimentation des index de texte intgral (FTS). Acquisition, escalade, libration et timeout de verrous*, et alertes de verrous mortels (deadlocks). Appels OLEDB effectus par SQL Server en tant que client : par exemple lors de requtes distribues sur des serveurs lis. Cration, modification et suppression dobjets de base (Ordres DDL CREATE, ALTER, DROP) Informations diverses de performance, notamment les plans dexcution dordres SQL. Information davancement dune opration dindexation en ligne (online indexing). Voir chapitre 6. vnements gnrs par la fonctionnalit Query Notifications. Voir encadr en fin de chapitre 8. Scans de tables et dindex. Ouvertures et fermeture de sessions, chec de connexion et divers audit de privilges et dexcution de commandes (BACKUP, DBCC). Principalement, changement de mmoire de linstance. Sessions ouvertes au moment du dmarrage de la trace. Excution de procdures stockes (T-SQL seulement), et dtail des ordres excuts dans la procdure. Ordres SQL envoys au serveur en batch. Aussi instructions XQuery. Dtail de la gestion des transactions. vnements utilisateur.

Certaines classes dvnements sont trs spcifiques et ne vous seront que dune utilit occasionnelle. Elles sont toutes dcrites en dtail dans les BOL, sous lentre SQL Server Event Class Reference , avec, pour chaque vnement, les colonnes retournes. Nous prsentons dans cet ouvrage les vnements importants pour lanalyse de performance, ceux que vous serez amens utiliser souvent. De mme, si nous nous concentrons sur les performances, quelques colonnes seulement sont utiles. Vous trouverez la liste des colonnes disponible dans les BOL, sous lentre Describing Events by Using Data Columns . Nous distinguerons les colonnes contenant des valeurs indiquant les performances, de celles affichant des informations sur lvnement, et qui vous permettront par exemple de grouper ou de filtrer les rsultats.

5.2 SQL Trace et le profiler

101

Vous pouvez gnrer vos propres vnements dans votre code SQL, laide de la procdure stocke sp_trace_generateevent. Vous pouvez passer dix vnements diffrents, numrots en interne de 82 91 (ce sont donc ces ID que vous passez en paramtre la procdure sp_trace_generateevent), et que vous retrouvez dans le profiler sous User Configurable : UserConfigurable:0 UserConfigurable:9. Voici un exemple de dclenchement dvnement :
DECLARE @userdata varbinary(8000) SET @userdata = CAST('c''est moi' as varbinary(8000)) EXEC sys.sp_trace_generateevent @event_class = 82, @userinfo = N'J''existe !!', @userdata = @userdata

Cet vnement sera visible comme UserConfigurable:0. Le contenu de @userinfo sera affich dans la colonne TextData et le contenu de @userdata dans la colonne BinaryData.

vnements
Les vnements les plus utiles sont : Database\Data File Auto Grow : une augmentation automatique de la taille dun fichier de donnes sest produite. La base de donnes affecte est visible dans la colonne DatabaseID. Database\Log File Auto Grow : une augmentation automatique de la taille dun fichier de journal sest produite. La base de donnes affecte est visible dans la colonne DatabaseID. Errors And Warnings\Attention : se dclenche lorsquun client est dconnect brusquement. Errors And Warnings\Exception : erreurs retournes par SQL Server au client, plus de dtails plus loin. Errors And Warnings\Execution Warnings : avertissements dclenchs par SQL Server lexcution de la requte. Errors And Warnings\Missing Column Statistics : type spcial davertissement, il manque des statistiques sur une colonne (voir la section 6.4). Errors And Warnings\Sort Warnings : avertissement sur une opration de tri qui na pu tre compltement excute en mmoire, et a donc bav dans tempdb (voir la section 8.1.2). Locks : vnements sur les verrous. Les vnements de pose de verrous sont trop frquents pour tre utiles. Les vnements de timeout vous permettent de tracer des attentes trop longues annules par une valeur de LOCK_TIMEOUT de la session (voir la section 7.2). Les vnements de verrou mortel (deadlock) seront dtaills dans la section 7.5.2). Performance\Auto Stats : cration ou mise jour automatique de statistiques (voir section 6.4).

102

Chapitre 5. Analyse des performances

Performance\ShowPlan : affichage du plan dexcution (voir section 8.1). Server\Server Memory Change : modification de la mmoire vive utilise par SQL Server, peut tre utile pour tracer une pression mmoire, en alerte de lagent SQL, par exemple ; Stored Procedures\RPC:Completed : un appel RPC (Remote Procedure Call appel de procdure depuis un client, avec la syntaxe CALL, ou un objet StoredProcedure de la bibliothque cliente). Contient les statistiques dexcution (dure, lectures). Stored Procedures\SP:Completed : fin de lexcution dune procdure stocke. Contient les statistiques dexcution (dure, lectures). Stored Procedures\SP:Recompile : recompilation dune procdure ou dune partie de celle-ci (voir la section 9.1). Stored Procedures\SP:StmtCompleted : analyse de lexcution de chaque instruction dune procdure stocke. Contient les statistiques dexcution (dure, lectures). TSQL\SQL:BatchCompleted : fin de lexcution dun batch de requtes. Contient les statistiques dexcution (dure, lectures). TSQL\SQL:StmtCompleted : fin de lexcution dune instruction dans un batch. Contient les statistiques dexcution (dure, lectures).

Colonnes
Les colonnes peuvent servir filtrer ou regrouper vos vnements. Vous pouvez crer de multiples filtres. Sans ces filtres, une trace en production est pratiquement illisible : peine lavez-vous dmarre, que des milliers, voir des dizaines de milliers dvnements sont dj tracs. Filtrer est non seulement indispensable pour la visibilit des informations, mais pour diminuer limpact de la trace. En effet, le filtre sera appliqu du ct du serveur, et allgera donc la trace. Pour filtrer vous pouvez soit cliquer sur le bouton Column filters de longlet Events Selection de la fentre de proprits de la trace, soit en cliquant directement sur len-tte dune colonne dans cette mme fentre. Une bote de dialogue de filtre (figure 5.5) souvre.

5.2 SQL Trace et le profiler

103

Figure 5.5 Les filtres de colonnes

Vous pouvez accumuler les filtres sur une mme colonne, avec diffrents oprateurs. Les colonnes filtres montrent une icne dentonnoir. Avant SQL Server 2005, les vnements qui ne retournaient rien dans la colonne filtre taient affichs quand mme, ce qui tait assez peu pratique. Depuis SQL Server 2005, loption Exclude rows that do no contain values permet de filtrer ces vnements. Pensez-y, elle nest pas slectionne par dfaut. Quelques ides de filtre : filtre sur un SPID particulier pour tracer une seule session, filtre sur une ApplicationName pour analyser ce que fait une application cliente, filtre sur TextData pour trouver les instructions SQL qui sappliquent un objet. Dans ce cas, la syntaxe du LIKE est la mme quen SQL : le caractre % (pourcent) remplace une chane de caractres. Pour une trace destine dtecter les requtes les plus coteuses, un filtre sur le nombre de reads (oprateur gal ou plus grand que) est la meilleure mthode : les reads ne varient pas dun appel un autre donnes gales, et plus le nombre de reads est important, plus linstruction est lourde, non seulement pour sa propre excution, mais aussi, travers les ressources quelle consomme et les verrous quelle pose peut-tre, pour toutes les autres sexcutant simultanment. Vous pouvez aussi grouper laffichage, par colonne(s). Cela vous permet dobtenir une vue non plus chronologique, mais dans un ordre qui vous siet mieux. Dans ce cas, la lecture peut tre moins intuitive. Cliquez sur le bouton Organize columns (ou clic droit sur les en-ttes de colonne, et commande organize columns ). Dans la fentre qui souvre, les boutons Up et Down permettre dorganiser lordre des colonnes. Un Up jusquau nud Groups cre un regroupement. Sur la figure 5.6, vous voyez un regroupement par ApplicationName, puis SPID.

104

Chapitre 5. Analyse des performances

Figure 5.6 Regroupement par colonnes

Laffichage en temps rel triera les vnements par ces colonnes de regroupement, vous naurez donc plus une vision strictement chronologique. De mme, ces colonnes seront toujours visibles gauche, mme si vous dfilez sur la droite pour afficher plus de colonnes. Si vous ne slectionnez quune seule colonne de groupement, vous obtiendrez une vision arborescente, rduite par dfaut.

Colonnes utiles aux performances


CPU : indique le temps processeur consomm par lvnement, en millisecondes. Si ce temps processeur est suprieur la valeur de Duration, votre requte a t paralllise sur plusieurs CPU. Duration : indique la dure totale dexcution de lvnement, y compris le temps denvoi du rsultat au client. Reads : indique le nombre de pages lues par la requte. Il sagit bien dunits de pages de base de donnes, de 8 Ko chacune. Pour obtenir le nombre doctets lus, vous pouvez multiplier cette valeur par 8192. Notez que vous ne pouvez pas obtenir le dtail spar des pages lues du buffer et rcupres du disque qui forment ce nombre total de reads. Cette information nest disponible que dans les statistiques rcupres en excutant une requte avec loption de session SET STATISTICS IO ON dans un client comme SSMS, ou en corrlant vos requtes avec des compteurs du moniteur de performances. RowCounts : nombre total de lignes affectes (lues, insres, modifies, supprimes) par lordre, le batch, ou la procdure. TextData : texte de lordre SQL excut. Writes : nombre de pages de donnes crites.

5.2 SQL Trace et le profiler

105

Ces colonnes indiquant des mesures chiffres de lexcution dordres SQL, elles ne sont bien entendu retournes que par les vnements dclenchs la fin dun ordre. Dans les couples dvnements tels que SQL:BatchStarting et SQL:BatchCompleted ou SP:Starting et SP:Completed, cest lvnement Completed qui vous intressera. Lvnement Starting ne sera utile que si vous dsirez observer ce qui se passe entre le dbut et la fin de lordre (par exemple laide de lvnement SP:StmtCompleted qui dtaille lexcution des parties dune procdure stocke), ou si, cause dune exception, votre ordre ne se termine pas correctement.

Attention Les colonnes CPU et Duration enregistrent en interne des dures en microsecondes (un millionime de seconde, 10 puissance -6). Elles sont affiches dans le profiler en millisecondes, comme pour les versions prcdentes de SQL Server, mais lorsque vous travaillez hors du profiler, par exemple dans une trace sauvegarde dans une table, ces valeurs seront en microsecondes. Vous devez donc diviser par 1000 pour obtenir des millisecondes.

Dautres colonnes sont indicatives, et utiles galement pour filtrer vos vnements : ApplicationName : nom de lapplication cliente, passe dans la chane de connexion. Par exemple SSMS se fait connatre sous le nom Microsoft SQL Server Management Studio. Si vous dveloppez des applications clients maison , il est utile de le spcifier, soit dans la chane de connexion (en y ajoutant Application Name= ), soit laide de la proprit idoine de votre bibliothque client (par exemple la proprit ApplicationName de lobjet SqlConnectionStringBuilder en ADO.NET), cela vous permettra de savoir do viennent vos requtes, et dafficher les vnements provenant dune application seulement. Les dveloppeurs peuvent aussi gnrer cet ApplicationName en y incluant le numro de version de leur produit. Cela rend ais la vrification des mises jour sur tous les clients, et cela permet, par exemple, de crer un dclencheur DDL qui vrifie cette valeur laide de la fonction systme APP_NAME() et interdit louverture de session si la version du client nest pas suffisante. DatabaseID : lidentifiant de base de donnes courante, utile pour nafficher que les requtes effectues dans une base de donnes. Vous pouvez trouver cet ID dans la colonne database_id de la vue systme sys.databases, ou laide de la fonction DB_ID(). Notez quil sagit du contexte de base dans lequel lordre est excut, et non la localisation des objets touchs. Si vous requtez la table AdventureWorks.Person.Contact depuis la base master, en utilisant comme ici le nom complet base.schma.objet, votre DatabaseID sera 1 (valeur toujours attribue la base master). DatabaseName : nom de la base de donnes en toutes lettres. Plus intuitif que DatabaseID.

106

Chapitre 5. Analyse des performances

GroupID : en SQL Server 2008, identifiant du groupe de ressources du gouverneur de ressources. Voir la section 4.5. HostName : nom de la machine cliente qui a ouvert la session. Correspondant au rsultat de la fonction niladique HOST_NAME(). IndexId : identifiant de lindex. Correspond la colonne index_id de la vue systme sys.indexes. Utile pour tracer les scans. IsSystem : indique si lvnement est dclench par une session systme. Valeur 0 sil sagit dune session utilisateur. Trs utile pour filtrer les vnements. LineNumber : contient le numro de ligne de lordre, dans le batch ou dans la procdure stocke. Sur les erreurs, indique la ligne sur laquelle elle sest produite. LoginName : contient le nom de la connexion (login) qui a ouvert la session. Nom complet avec domaine en cas de connexion Windows intgre (le nom simple sera alors rcuprable dans la colonne NTUserName). ObjectID : identifiant de lobjet. Correspond la colonne object_id des vues systmes sys.objects ou sys.tables. Utile pour tracer les scans. SPID : identifiant de session attribu par SQL Server. Correspond la valeur rcupre par la variable systme @@SPID. Utile pour filtrer une trace sur une seule session. Attention cependant : ds la session ferme, ce mme numro peut tre rattribu par SQL Server une nouvelle session.

Dtection dexceptions
Les vnements de la catgorie Errors and Warnings tracent les erreurs et exceptions gnres par SQL Server. Cest un bon moyen de surveiller votre serveur. Parfois, certaines applications clientes provoquent des erreurs qui ne sont pas rcupres dans leur code, ce qui fait que ces erreurs restent invisibles. Parfois galement, des erreurs se produisent, et les utilisateurs pressent la touche ENTRE sur des messages difficiles comprendre, sans appeler le support informatique. Ou, lorsque vous dfinissez vos propres erreurs, vous aimeriez savoir quand elles se dclenchent. La meilleure manire de rcuprer une erreur occasionnelle lorsque vous vous y attendez, est de dfinir une alerte dans lagent SQL. Vous pouvez vous faire notifier par lagent du dclenchement de toute erreur, par sa gravit, son numro ou un texte quelle contient. La programmation dune alerte est hors du sujet de ce livre, nous vous montrons simplement, sur la figure 5.7, un exemple dalerte dclenche sur une erreur de gravit 23, dclenche dans le contexte de la base AdventureWorks. La rponse cette alerte peut tre lenvoi dune notification par e-mail ou NET SEND, ou lactivation dun travail de lagent.

5.2 SQL Trace et le profiler

107

Figure 5.7 Alerte sur vnement de trace

Vous pouvez aussi, de temps en temps, tracer votre serveur laide du profiler, pour vous assurer quaucune erreur ne passe indtecte. Les erreurs gnres par SQL Server et renvoyes la session cliente, sont appeles exceptions. Cela peut tre linformation quun objet dclar dans le code nexiste pas, la violation dune contrainte, etc. Vous rcuprez le numro derreur dans lvnement Exceptions, et le message derreur tel quil est envoy au client, dans lvnement User Error Message. Vous voyez sur la figure 5.8 un exemple de trace derreur. La signification des numros derreur est indique dans les BOL.

Figure 5.8 Rcupration des erreurs par le profiler

5.2.1 Utiliser le rsultat de la trace


Une trace sauvegarde dans un fichier ou dans une table peut tre ouverte laide du profiler (menu File / Open / Trace File ou Trace Table). Les lignes peuvent tre filtres et groupes comme sur une trace en temps rel. Pour raliser une analyse approfondie des rsultats de la trace, la meilleure solution est denregistrer ce rsultat dans une table SQL. Pour ce faire, rechargez le fichier de trace dans le profiler, puis sauvez le rsultat dans une table laide de la commande File / Save As / Trace Table La trace peut aussi tre insre dans une table directement depuis SQL

108

Chapitre 5. Analyse des performances

Server, laide de la fonction fn_trace_gettable(). Cette fonction accepte deux paramtres, le nom du fichier de trace, et le nombre de fichiers importer en cas de prsence de fichiers de rollover :
SELECT * INTO dbo.matrace FROM sys.fn_trace_gettable('c:\temp\matrace.trc', default);

Lit les fichiers de trace dont le nom de base est matrace.trc et les insre dans la table dbo.matrace. La valeur 'default' indique que tous les fichiers de rollover prsents doivent tre insrs. Ensuite, rien nest plus facile que de lancer des requtes sur la table pour obtenir les informations dsires. Voici par exemple une requte listant par ordre dcroissant les requtes les plus coteuses :
SELECT CAST(TextData as varchar(8000)) as TextData, COUNT(*) as Executions, AVG(reads) as MoyenneReads, AVG(CPU) as MoyenneCPU, AVG(Duration) / 1000 as MoyenneDurationMillisecondes FROM dbo.matrace WHERE EventClass IN ( SELECT trace_event_id FROM sys.trace_events WHERE name LIKE 'S%Completed') GROUP BY CAST(TextData as varchar(8000)) ORDER BY MoyenneReads DESC, MoyenneCPU DESC;

Pour ne prendre que les requtes, nous cherchons dans la vue sys.trace_events les vnements qui commencent par 'S' (SQL ou SP) et qui se terminent par 'Completed'.

Rejouer une trace


Une trace peut tre rejoue, cest--dire que les vnements quelle contient peuvent tre rexcuts sur un serveur SQL, si toutefois les vnements contenus dans la trace sont rejouables. Pour crer une trace rejouable, le plus simple est dutiliser le modle TSQL_Replay, qui ajoute tous les vnements ncessaires une trace entirement rejouable. Lorsque vous ouvrez une trace enregistre, un menu Replay est ajout, ainsi quune barre doutils (voir figure 5.9).

Figure 5.9 Boutons de replay

5.2 SQL Trace et le profiler

109

Vous pouvez poser un point darrt sur une ligne dvnement (touche F9), faire du pas pas (F10), ou excuter la trace jusqu la ligne slectionne (CTRL+F10). Lorsque vous lancez lexcution, une fentre permet de choisir les options, disposes dans deux onglets.

Figure 5.10 Premier onglet des options de replay

Dans le premier, reproduit sur la figure 5.10, vous indiquez si le rsultat de lexcution doit tre enregistr dans un fichier ou une table. Si ces deux options sont dcoches, il sera simplement affich dans le profiler. En utilisant les fonctionnalits de multi-threading, vous pouvez faire excuter des instructions en parallle. Le profiler rejoue les vnements sur plusieurs threads, groups par SPID, dans lordre de la colonne EventSequence. Vous reproduisez donc un environnement multi-utilisateurs. La trace est joue plus vite, mais vous perdez les possibilits de dbogage (point darrt, pas pas).

110

Chapitre 5. Analyse des performances

Figure 5.11 Deuxime onglet des options de replay

Dans le premier, reproduit sur la figure 5.11, vous dsactivez lexcution dinstructions lance par des sessions systme pour ne garder que le comportement des sessions utilisateur. Vous pouvez nexcuter que les instructions dun SPID, en sachant que le mme SPID peut tre rutilis par des sessions diffrentes, lune aprs lautre. Vous pouvez indiquer les intervalles de temps des vnements de la trace qui doivent tre rejous. La partie Health Monitor permet de configurer un thread du Profiler pour tuer automatiquement les processus qui sont bloqus depuis un nombre dfini de secondes. La valeur dattente 0 signifie que le profiler ne tuera aucun processus bloqu. Nous traitons de la surveillance de processus bloqus dans la section 7.5.

Test de charge
La capacit de rejouer des traces est intressante pour faire un test de charge, sur un serveur de test ou sur un serveur final avant une mise en production, laide dune trace reprsentative de lactivit relle ou prvue du serveur. Vous pouvez ouvrir plusieurs fois la mme trace, et la rejouer partir dinstances diffrentes du profiler, ou mme de machines clientes diffrentes. Cette approche pose un problme : il faut sassurer que la trace ne comporte pas dinstructions qui ne peuvent tre rejoues plus dune fois, notamment des insertions explicites de valeurs vrifies par des contraintes dunicit. Si cest le cas, vous pouvez soit les retirer de la trace, soit les encapsuler dans des transactions que vous terminez en ROLLBACK. Dans les deux cas, la gestion du replay devient plus, voire trop, contraignante.

5.2 SQL Trace et le profiler

111

Deuxime point : comment automatiser les replay ? Le profiler (profiler90.exe en SQL Server 2005) comporte quelques paramtres dappel en ligne de commande (faire profiler90.exe /? pour les voir), mais qui ne permettent pas de lancer une trace directement en replay. Une solution peut tre dexporter le contenu des ordres SQL, par la commande File / Export / Extract SQL Server Events, qui gnre un fichier de batches avec extension .sql. Vous pouvez ensuite excuter loutil en ligne de commande sqlcmd.exe avec le paramtre -i :
sqlcmd.exe -E -S monserveur -i c:\temp\monbatch.sql

que vous pouvez lancer de multiples fois dans un script, dans une boucle par exemple. Une autre solution consiste utiliser les outils que lquipe de support utilisateur de Microsoft a dvelopps pour faciliter lutilisation de traces pour les tests de charge. Ils sont packags sous le nom de RML Utilities for SQL Server, et peuvent tre tlchargs sur le site de Microsoft (cherchez avec le nom du package). Ils ncessitent le Framework .NET 3.5, quils installent automatiquement si ncessaire. Ces outils ne sont pas compatibles SQL Server 2008, notamment cause des nouveaux types de donnes. Au moment de la rdaction de ce livre, la version 2008 est en dveloppement, elle sera donc disponible tt ou tard.

5.2.2 Diminution de limpact de la trace


Comme nous lavons dit, le profiler nest quun client de la fonctionnalit serveur nomme SQL Trace. Le profiler est trs utile en tant quoutil doptimisation, de dbogage, et pour essayer, en temps rel, de dcouvrir la source dun problme de performances. Son dfaut est quil utilise des ressources supplmentaires, soit du temps processeurs lorsquil est lanc sur le serveur, soit de la bande passante rseau sil tourne sur un poste client (ce qui est meilleur). Lorsque vous souhaitez planifier des traces, ou excuter des traces de longue haleine, par exemple pour construire une baseline, ou pour identifier travers une priode reprsentative les requtes les plus coteuses, la solution la plus souple et la moins consommatrice de ressources, est de maintenir la trace mme SQL Server. Pour cela, vous disposez de quelques procdures stockes systme. Vous pouvez exporter la dfinition de la trace dfinie dans le profiler en ordres SQL, ce qui rend trs facile la cration de scripts de trace. Dans le profiler, choisissez dans le menu File la commande Export / Script Trace Definition, puis votre version de SQL Server. Voici un exemple simplifi de script gnr :
declare @rc int declare @TraceID int declare @maxfilesize bigint set @maxfilesize = 5 exec @rc = sp_trace_create @TraceID output, 0, N'InsertFileNameHere', @maxfilesize, NULL if (@rc != 0) goto error declare @on bit

112

Chapitre 5. Analyse des performances

set @on = 1 exec sp_trace_setevent @TraceID, 14, 1, @on -- ... -- Set the Filters declare @intfilter int declare @bigintfilter bigint exec sp_trace_setfilter @TraceID, 10, 0, 7, N'SQL Server Profiler - 05b0b8f9-5048-4129-a0c1-9b7782ba8e6c' -- Set the trace status to start exec sp_trace_setstatus @TraceID, 1 -- display trace id for future references select TraceID=@TraceID

sp_trace_create : cre une trace en retournant en paramtre OUTPUT son identifiant numrique. Vous indiquez en paramtre le chemin du fichier dans lequel la trace sera enregistre. Vous pouvez galement indiquer la taille du fichier et le nombre maximum de fichiers de rollover, ainsi que la date et heure darrt de la trace. sp_trace_setevent : ajoute un vnement partir de son identifiant. Celui-ci peut tre retrouv dans la vue sys.trace_events. Exemple de requte pour lister les vnements :
SELECT tc.name as categorie, te.name as evenement, trace_event_id FROM sys.trace_categories tc JOIN sys.trace_events te ON tc.category_id = te.category_id ORDER BY categorie, evenement;

sp_trace_setfilter : ajoute un filtre. Les paramtres indiquent lID dvnement, un numrique indiquant un boolen pour organiser les filtres sur un mme vnement (AND ou OR entre les filtres), un numrique indiquant loprateur de comparaison, et la chane UNICODE comparer. sp_trace_setstatus : dmarre (0), arrte (1) ou supprime (2) la trace. Cest ce qui vous donne le contrle sur lexcution de votre trace. La vue systme sys.traces vous permet de retrouver la liste des traces dfinies. Lorsque vous avez une trace dfinie, vous pouvez planifier son excution laide de sp_trace_setstatus, par exemple dans des travaux de lagent SQL.

Traces systme
SQL Server possde deux traces spciales, quil gre lui-mme. La premire est appele la trace par dfaut (default trace). Elle porte lidentifiant 1 et est active par dfaut. Elle nest pas liste par sys.traces, mais vous pouvez utiliser la fonction fn_trace_getinfo pour la voir :
SELECT * FROM fn_trace_getinfo(1);

5.3 Moniteur systme

113

Cette trace na pas de nom, elle est utilise en interne pour alimenter des rapports, et peut tre utilise en cas de crash, pour aider en identifier la cause. Elle est crite par dfaut dans le rpertoire de donnes, sous \LOG\log.trc (avec un nommage en rollover). Elle est peu coteuse et peut tre conserve. Si vous voulez la dsactiver pour profiter des moindres moyens doptimiser un serveur trs charg, vous pouvez changer loption de serveur 'default trace enabled' 0 :
EXEC sp_configure 'show advanced options' , 1 RECONFIGURE EXEC sp_configure 'default trace enabled', 0 EXEC sp_configure 'show advanced options' , 0 RECONFIGURE

La seconde est une trace que vous pouvez activer, appele la bote noire (blackbox trace). Comme son nom lindique, elle vous permet de conserver un historique rcent des oprations ralises sur le serveur, afin doffrir des informations en cas de dfaillance. Elle est un peu plus lourde que la trace par dfaut, car elle recueille plus dvnements. Si vous ne voulez pas avoir une bote noire permanente, vous pouvez lactiver en cas de suspicion de problme, ou pour dboguer des crashs rguliers. Pour cela, crez une trace avec sp_trace_create, et le paramtre @Options = 8 :
DECLARE @traceId int EXEC sys.sp_trace_create @traceId OUTPUT, @Options = 8; EXEC sys.sp_trace_setstatus @traceId, 1; SELECT @traceId;

Vous pouvez modifier le chemin dcriture du fichier, et la taille de celui-ci (5 Mo nest pas toujours suffisant), avec les paramtres de sp_trace_create. Pour vous assurer que la bote noire tourne toujours, vous pouvez automatiser son dmarrage en encapsulant son activation par sp_trace_setstatus dans une procdure stocke, et en faisant en sorte que cette procdure se lance au dmarrage du service, laide de la procdure sp_procoption :
EXEC sys.sp_procoption 'ma_procedure', 'STARTUP', 'ON';

5.3 MONITEUR SYSTME


Le moniteur systme, ou moniteur de performance, nest pas propre SQL Server. Cest un utilitaire de supervision intgr Windows, qui est comme le moniteur cardiaque du systme. Il vous permet de suivre en temps rel tous les compteurs des donnes chiffres indiquant le comportement de votre serveur. Un certain nombre dapplications, dont SQL Server, ajoutent leur propre jeu de compteurs ceux proposs par Windows. Vous pouvez ainsi, dans la mme interface, suivre les signes vitaux de Windows et de SQL Server. Vous trouvez le moniteur systme soit dans le menu Windows, sous Outils dadministration/performances, soit en lanant lexcutable perfmon.exe. louverture, quelques compteurs sont dj prslectionns, il vous appartient de les retirer si

114

Chapitre 5. Analyse des performances

vous nen avez pas besoin, et de choisir les vtres. Vous pouvez soit afficher lvolution des compteurs en temps rel, soit enregistrer les mesures dans un fichier journal. Cette dernire solution est idale pour construire une baseline. Laffichage peut tre graphique (courbes dvolution), ce qui est utile pour les compteurs dont il est intressant de suivre la progression dans le temps, ou sous forme de tableau, pour obtenir la valeur actuelle prcise. Vous pouvez galement, pour chaque compteur, modifier lchelle de sa ligne dans le graphe, afin de pouvoir mlanger des units de mesures diverses sur le mme affichage, ainsi que la couleur et lpaisseur de son trait.

Figure 5.12 cran par dfaut du moniteur de performances.

Attention aux diffrences dchelle daffichage dans le graphe. Il est facile de se mprendre en comparant deux mesures des chelles diffrentes. Vous pouvez modifier la valeur dchelle dans les proprits du compteur, onglet Data, liste de choix scale .

Pour ajouter des compteurs, cliquez sur le bouton affichant le signe + dans la barre doutils, ou utilisez la combinaison de touches CTRL+I. Vous obtenez ainsi la fentre de slection de compteurs. Vous pouvez slectionner de quelle machine vous voulez obtenir les compteurs, avec la syntaxe \\NOM_MACHINE. Lorsque vous slectionnez un compteur, le bouton explain... permet dobtenir une petite description.

5.3 Moniteur systme

115

Les compteurs sont regroups par objets, et certains sappliquent des instances. Lobjet est une collection de compteurs qui sappliquent au mme sous-systme, par exemple les processeurs (CPU), les disques physiques, la mmoire vive, des modules de SQL Server Les instances reprsentent les units de ces sous-systmes que le compteur mesure. Par exemple, lobjet processeur a autant dinstances que le systme compte de processeurs logiques (deux instances pour un Dual Core ou un processeur hyper-thread), ou lobjet disque logique a autant dinstances quil existe de partitions. Si vous voulez recueillir un compteur pour toutes les instances dun objet, vous pouvez soit slectionner le bouton radio all instances , soit choisir une instance qui regroupe les autres, la plupart du temps prsente, et qui porte le nom _Total. Pour certains lments, il peut tre intressant dafficher la fois un compteur par instance, et un compteur total. Vous obtenez une bonne perception du comportement du multitche, par exemple, en visualisant une ligne par CPU individuel et une moyenne sur une ligne dont vous augmentez lpaisseur. Cela vous permet en un coup dil de juger de lquilibre de la charge de travail accord par le systme chaque CPU. Chaque objet dispose de compteurs spcifiques, qui sont les pourvoyeurs dinformations chiffres. Ces informations sont exprimes soit en valeur courante (par exemple : LogicalDisk/Current Disk Queue Length), en moyenne (LogicalDisk/Avg Disk Queue Length), en pourcentage (LogicalDisk/% Disk Read Time) ou en totaux cumuls (Memory/Committed Bytes), soit en quantit par secondes (LogicalDisk/ Disk Reads/sec). Comme vous pouvez afficher dans la mme fentre des compteurs provenant de machines diffrentes, le contexte complet du compteur est constitu de quatre parties : \\Ordinateur\Objet(Instance)\Compteur. Il nous est arriv, lorsque nous avions deux crans disposition, de conserver une fentre du moniteur systme ouverte en permanence sur notre second cran, avec quelques compteurs essentiels montrant ltat dun serveur SQL. Dans ce cas, nous choisissions un dlai de rafrachissement de 5 ou 10 secondes, afin de ne pas trop impacter le serveur. Il est noter quune utilisation du moniteur sur une machine cliente est moins pnalisante quune utilisation locale au serveur. Dans ce cas, nous cachions tous les lments daffichage inutiles, pour ne conserver quune vue de rapport, comme reproduit sur la figure 5.13.

116

Chapitre 5. Analyse des performances

Figure 5.13 Affichage minimal

Lorsque vous avez obtenu une vision satisfaisante, et que vous avez choisi les bons compteurs, vous pouvez sauver cette configuration dans un fichier de configuration de console (Le moniteur de performance est un composant logiciel enfichable (snap-in) de loutil Windows MMC, Microsoft Management Console), portant lextension .msc. Cela vous permet de rouvrir trs rapidement des jeux de compteur. Vous pouvez vous faire une deuxime fentre montrant lactivit des processeurs en affichage graphe. Voici un script WSH (Windows Script Host) que vous pouvez utiliser pour lancer en une seule commande plusieurs fentres de moniteur partir de fichiers .msc (deux dans lexemple) :
Option Explicit Dim oWsh : Set oWsh = CreateObject("WScript.Shell") oWsh.Run "mmc ""C:\mypath\CPU.msc""" oWsh.Run "mmc ""C:\mypath\SQL MEMORY AND DISK.msc""" Set oWsh = Nothing

Nous allons passer en revue les indicateurs utiles pour suivre lactivit de SQL Server.

5.4 Choix des compteurs

117

5.4 CHOIX DES COMPTEURS


Pour obtenir des informations sur les performances de SQL Server, vous devez panacher des compteurs fournis par le systme dexploitation, et des compteurs propres SQL Server. Certains compteurs du systme dexploitation ne peuvent donner des informations prcises, par exemple sur lutilisation du cache de mmoire. Nous avons parl de la couche SQLOS. SQL Server vous fournit donc des compteurs spcifiques qui permettent dexplorer plus en dtail ce que fait SQLOS. Nous allons sparer les compteurs essentiels, qui donnent des informations vitales sur la sant du serveur, des compteurs simplement utiles.

5.4.1 Compteurs essentiels


MSSQL:Buffer Manager\Buffer cache hit ratio
Ce compteur indique le pourcentage de pages lues par le moteur de stockage, qui ont pu tre servies depuis le cache de donnes (le buffer), sans accder au disque, calcul sur les quelques derniers milliers de pages demandes. Ce ratio doit tre lev : moins SQL Server doit accder au disque, plus il sera rapide. On considre dans une application OLTP volumtrie moyenne, que cette valeur doit tre au-dessus de 97 ou 98 %, ce qui est dj peu. Un bon chiffre est au-dessus de 99,7 %. Si le ratio est bas, et que vous observez galement une activit et une file dattente importantes sur le disque, augmentez la mmoire vive de votre systme (ou cherchez des scans de table qui peuvent tre limins par la cration dindex).

MSSQL:Databases\Transactions/sec
Nombre de transactions par seconde. Le choix de linstance de compteur permet dafficher toutes les transactions, ou les transactions dans un contexte de base particulier, ce qui est utile pour tracer lactivit comparative de vos bases de donnes, et lactivit dans tempdb. Une utilisation rgulire de ce compteur vous permet de connatre lactivit moyenne de votre serveur, travers le temps et durant les priodes de la journe, ainsi que de dtecter les augmentations de charge. Une valeur leve de transactions par seconde, avec des rsultats raisonnables dautres compteurs (utilisation du disque, pourcentage dactivit des processeurs) indique une machine qui travaille bien. Un bon signe de leffectivit de vos efforts doptimisation dun serveur est laugmentation du nombre de transactions par seconde, lie la diminution de la charge des processeurs. Une quantit importante de transactions dans tempdb indique gnralement soit un recours frquent aux tables temporaires dans le code, soit un fonctionnement en niveau disolation snapshot. Vous pouvez vrifier que cette activit nest pas due une cration de tables de travail internes, destines rsoudre des plans de requte, en observant le compteur MSSQL:Acces Methods\Worktables Created/sec, qui indique le nombre de tables de travail cres par seconde. Les tables de travail stockent des rsultats intermdiaires lors de lexcution de code SQL. Vous pouvez aussi vous

118

Chapitre 5. Analyse des performances

baser sur le compteur MSSQL:General Statistics\Active Temp Tables pour connatre le nombre de tables temporaires dans tempdb, ou MSSQL:General Statistics\Temp Tables Creation Rate pour une quantit de tables temporaires utilisateur cres par seconde. Rfrez-vous la section ddie tempdb pour plus de dtails.

MSSQL:General Statistics\Temp Tables Creation Rate


Indique le nombre de tables temporaires utilisateurs cres par seconde. Le compteur MSSQL:General Statistics\Temp Tables For Destruction retourne le nombre de tables temporaires marques pour suppression pour le thread de nettoyage. Ces compteurs vous donnent une bonne ide du poids des tables temporaires dans votre code SQL, et des problmes potentiels quelles peuvent produire (contention des tables de mtadonnes dans tempdb, par exemple, voir section 8.3.1).

PhysicalDisk\Avg. Disk Queue Length


Longueur moyenne de la file dattente sur un disque. Le disque dur tant accs squentiel (une seule tte de lecture, donc une seule lecture/criture la fois), les processus qui veulent accder au disque lorsque celui-ci est dj en utilisation, sont placs dans une file dattente. Il sagit donc de temps perdu faire la queue, comme aux caisses dun supermarch. Dans lidal, ce compteur devrait toujours tre 0, voire 1, avec des possibilits de pointes. Si la valeur est de faon consistante 2 ou plus, vous avez un goulot dtranglement sur le disque. Que faire ? Les possibilits sont, dans lordre logique daction : diminuer les besoins de lecture sur le disque, en optimisant les requtes, ou la structure physique de vos donnes, notamment en crant des index pour viter les scans ; ajouter de la RAM, pour augmenter la taille du buffer, et diminuer les besoins de lecture sur le disque ; dplacer les fichiers de base de donnes ou de journal de transaction les plus utiliss, sur dautres disques, pour partager la charge : faire du scale-out (rpartition de charge) ; utiliser un disque plus rapide, avec un meilleur temps daccs, un contrleur plus rapide, ou utiliser un sous-systme disque plus performant (SAN, stripe). En dautres termes, faire du scale-up.

Processor\% Processor Time


Pourcentage dutilisation des processeurs logiques (deux instances sur un Dual Core). La valeur retourne est soit la moyenne de tous les processeurs si vous affichez toutes les instances, soit par instance. Cest le compteur de base pour avoir une ide de dpart de la charge de votre serveur. Une machine dont les processeurs sont constamment proches du 100 % a un problme. Soit elle a atteint les limites de ses capacits, et il faut donc songer acqurir du nouveau matriel, que ce soit en scale-up ou en scale-out (rpartition de charge), soit un lment du systme entrane une surcharge. Vous amener identifier cet lment est un des objectifs de ce livre. Il est

5.4 Choix des compteurs

119

important de comprendre quune forte activit des processeurs ne signifie pas que la puissance ou la quantit des CPU doit tre augmente. Le travail du processeur est influenc par tous les autres sous-systmes. Si le disque est inutilement sollicit, ou trop lent, si la file dattente sur le disque implique de nombreuses attentes de threads, si la mmoire vive fait dfaut et entrane donc une plus forte utilisation du disque, etc. les CPU devront travailler plus. Donc, le temps processeur nest que la partie merge de liceberg, vous devez inspecter toutes les raisons possibles de cette surcharge de travail, avant de tirer vos conclusions.

5.4.2 Compteurs utiles


Memory\Pages/sec
Indique le nombre de pages de mmoire (dans le sens Windows du terme) qui sont crites sur le disque, ou lues depuis le disque. Un nombre lev (par exemple plus de 2 500 pages par secondes, de faon consistante sur un serveur de puissance moyenne, peut indiquer un manque de mmoire. corrler avec les compteurs de cache de SQL Server, et lactivit disque.

MSSQL:Access Methods\Forwarded Records/sec


Indique un nombre le lignes lues via un renvoi denregistrement par secondes. Cela peut permettre de dtecter un trop grand nombre de renvois denregistrements1. Sur une table sans index clustered, le moteur de stockage cre des pointeurs de renvoi lorsque la taille dune ligne modifie augmente (parce quelle contient des colonnes de taille variable), et quelle ne peut plus tenir dans sa page originelle. Ce pointeur reste en place jusqu un compactage (shrink) de la base de donnes ou jusqu ce que la ligne diminue suffisamment de taille pour rintgrer sa page dorigine. Un trop grand nombre de renvois diminue les performances dIO, cause de ltape supplmentaire de lecture du pointeur, et de dplacement sur son adresse. Cela ne devient problmatique sur une table que lorsquun bon pourcentage de la table contient des renvois denregistrements. Vous pouvez dtecter ces tables laide de la fonction de gestion dynamique sys.dm_db_index_physical_stats :
SELECT OBJECT_NAME(object_id) as ObjectName, index_type_desc, record_count, forwarded_record_count, (forwarded_record_count / record_count)*100 as forwarded_record_ratio FROM sys.dm_db_index_physical_stats( DB_ID('AdventureWorks'), NULL, NULL, NULL, DEFAULT) WHERE forwarded_record_count IS NOT NULL;

1. Bien que nous parlions de ligne lorsque nous voquons la couche logique des bases de donnes, le terme enregistrement est utilis lorsque nous parlons du moteur de stockage.

120

Chapitre 5. Analyse des performances

Attention Le dernier paramtre (mode) doit tre indiqu une valeur diffrente de LIMITED ou NULL (qui quivaut LIMITED), sinon toutes les valeurs de la colonne forwarded_record_count seront renvoyes NULL.

Ne vous inquitez pas dun nombre raisonnable de Forwarded Records/sec, cela peut arriver dans des tables temporaires sur tempdb. Vrifiez alors le nombre de tables temporaires, et le nombre de transactions dans tempdb. Si la valeur ne correspond pas une activit de tempdb, cherchez dans votre base utilisateurs quelles sont les tables heap qui contiennent des renvois denregistrements, laide de la fonction prcdente. Pour diminuer le nombre de renvois, vous pouvez soit faire un shrink (DBCC SHRINKDATABASE ou DBCC SHRINKFILE) de votre base de donnes, ce qui est une mauvaise solution en soi, sauf si vous faites un shrink sans diminuer la taille physique du fichier, laide de loption NOTRUNCATE :
DBCC SHRINKDATABASE (AdventureWorks, NOTRUNCATE);

Cela rorganise les pages sans diminuer la taille des fichiers. La vraie bonne solution reste toutefois de rorganiser la table en crant un index clustered, que vous pouvez supprimer ensuite si vous tenez absolument conserver une table heap (il ny aurait pas de raison, la table clustered est recommander)1.

Attention Les augmentations et diminutions automatiques des fichiers de donnes et de journal sont trs pnalisantes pour les performances, et gnrent une fragmentation importante. Elles sont viter.

MSSQL:Access Methods\Full Scans/sec


Indique un nombre de scans de table (table heap ou index clustered) par seconde. Le scan de table nest pas un problme en soi sur les petites tables, il est par contre trs pnalisant sur les tables partir dune taille raisonnable (sentend en nombre de pages. Une table contenant beaucoup de lignes contenant des colonnes de petite taille peut provoquer moins de lectures quune table ayant moins de lignes, mais contenant des colonnes plus grandes). Ce compteur est utile pour dtecter un manque dindex ou des requtes mal crites. Un scan provient soit dun manque index utile pour rsoudre la clause de recherche (quil soit absent ou trop peu slectif), soit de la prsence de requtes qui ne filtrent pas (ou mal) les donnes, par exemple de requtes sans clause WHERE. Pour plus de dtails, reportez-vous au chapitre 6.

1. Plus dinformations sur les problmes de performances possibles sur des renvois denregistrements dans cette entre de blog : http://blogs.msdn.com/mssqlisv/archive/2006/12/01/knowingabout-forwarded-records-can-help-diagnose-hard-to-find-performance-issues.aspx

5.4 Choix des compteurs

121

MSSQL:Access Methods\Page Splits/sec


Indique un nombre de sparations (split) de page par seconde. Un nombre lev de page splits indique que des index font face dintensives rorganisations dues des modifications de donnes sur leurs cls. Cela pourra vous pousser utiliser un FILLFACTOR (voir section 6.1) plus lev sur ces index, ou modifier votre stratgie dindexation. Pour chercher sur quels index les splits se sont produits, la fonction de gestion dynamique sys.dm_db_index_operational_stats peut vous donner des pistes. Pour plus de dtails, reportez-vous au chapitre 6.

MSSQL:Access Methods\Table Lock Escalations/sec


Retourne un nombre descalade de verrous par seconde. Pour plus dinformation sur les escalades de verrous, reportez-vous au chapitre 7. Pour identifier quelles requtes peuvent provoquer une escalade de verrous, vous pouvez tenter de corrler les pics de ce compteur avec une trace SQL (voir la corrlation de journal de compteur et de trace en section 5.3.4). La colonne index_lock_promotion_count de la fonction sys.dm_db_index_operational_stats est galement utile.

MSSQL:Buffer Manager\Database pages


Indique le nombre de pages de donnes caches dans le buffer. Sur un systme qui dispose de suffisamment de RAM, cette valeur devrait peu voluer. Si vous observez des changements importants de ce compteur, cela veut dire que SQL Server doit librer des pages du buffer. Il manque certainement de la mmoire vive (il en manque physiquement, ou loption max server memory limite son utilisation par SQL Server).

MSSQL:Buffer Manager\Free list stalls/sec


Indique la frquence laquelle des demandes doctroi de pages de buffer sont suspendues parce quil ny a plus de pages libres dans le cache. Cette valeur doit tre la plus petite possible. Une valeur dpassant 2 est un indicateur de manque de mmoire.

MSSQL:Buffer Manager\Page life expectancy


Indique le nombre de secondes pendant lesquelles une page de donnes va rester dans le buffer sans rfrences, cest--dire sans quun processus naccde cette page. Selon Microsoft, 300 secondes est la valeur minimum obtenir, et lidal est la valeur la plus leve possible. Si ce compteur indique 300, cela signifie quune page va tre vide du cache aprs 5 minutes moins quelle soit utilise dans ce laps de temps. SQL Server ajuste cette valeur selon la quantit de mmoire disponible pour le buffer. Moins il y a de mmoire, plus la dure de vie est courte, pour permettre SQL Server de librer de la place dans le buffer pour des donnes plus utiles. Cest donc un bon indicateur : si ce compteur tombe en dessous de 300, vous manquez manifestement de mmoire pour le buffer ou vous avez malencontreusement limit loption max server memory .

122

Chapitre 5. Analyse des performances

MSSQL:Buffer Manager\Page reads/sec et MSSQL:Buffer Manager\Page writes/sec


Indiquent le nombre de pages lues depuis le disque et crites sur le disque par le gestionnaire de cache de donnes. En corrlant ces valeurs avec vos informations provenant du disque, vous pouvez dterminer si lactivit disque est principalement gnre par SQL Server. De mme, plus ces valeurs sont leves, moins votre cache travaille efficacement. En dautres termes, plus vous manquez de mmoire vive.

MSSQL:Buffer Manager\Total pages


Nombre total de pages de cache de donnes. Vous trouvez lutilisation des pages dans dautres compteurs du mme groupe : dans database pages, les pages utilises pour les donnes, dans free pages, les pages disponibles, dans stolen pages les pages libres sous pression mmoire, pour dautres utilisations, en gnral le cache de procdures. Target pages indique le nombre idal de pages de buffer que SQL Server estime pour votre systme. La diffrence entre Target pages et Total pages devrait videmment tre aussi mince que possible.

MSSQL:Databases\Log growths
Nombre total daugmentations de la taille physique des fichiers de journal de transaction pour une base de donnes. Cela vous indique si la taille du journal choisie la base tait bonne. Laugmentation automatique du journal diminue les performances dcriture en fragmentant et en multipliant le nombre de VLF (Virtual Log Files).

MSSQL:Databases\Percent Log Used


Pourcentage du journal en utilisation. Nous vous conseillons de crer une alerte de lagent SQL testant cette valeur, pour vous notifier par exemple lorsquelle dpasse les 80 %, ou plus, selon votre systme.

MSSQL:General Statistics\Active Temp Tables


Nombre de tables temporaires utilisateur dans tempdb, un moment donn.

MSSQL:General Statistics\Processes Blocked


Ce compteur, nouveaut de SQL Server 2005, affiche le nombre de processus bloqus, cest--dire de processus qui attendent, depuis un certain temps, la libration de verrous tenus par un autre processus, pour continuer leur travail. Lorsquun verrou est maintenu trop longtemps, il peut provoquer un embouteillage de verrous, les processus sattendant les uns sur les autres. Vous pouvez utiliser ce compteur dans une alerte du moniteur systme ou de lagent SQL pour vous avertir de cette situation (une augmentation rapide et continue de ce compteur en tant le signe). Vous avez dautres moyens de dtection de cette situation, comme nous le verrons dans la section 7.5.1.

5.4 Choix des compteurs

123

MSSQL:General Statistics\User Connections


Nombre de sessions ouvertes. Une augmentation et une stabilisation de ce nombre peuvent indiquer des problmes lis une application qui ne gre pas bien les dconnexions.

MSSQL:Locks\Average Wait Time (ms)


Nombre moyen dattentes pour la libration de verrous, en millisecondes. Une valeur leve indique une contention due des transactions ou des requtes trop longues, qui verrouillent les donnes trop longtemps. Le meilleur remde est de modifier le code, pour diminuer ltendue des transactions et la dure des requtes, ou leur niveau disolation. Si cela nest pas possible, il peut tre utile de passer la base de donnes dans laquelle le verrouillage est important, en mode READ COMMITTED SNAPSHOT (voir section 7.3).

MSSQL:Locks\Number of Deadlocks/sec
Nombre de verrous mortels par secondes. Si cette valeur est rgulirement au-dessus de 0, vous avez un rel problme. Reportez-vous la section 7.5.2.

MSSQL:Plan cache\Cache Objects Count


Nombre dobjets de cache par type de cache. Linstance SQL Plans indique le cache de plans dexcution. Attention la diminution violente de cette valeur : signe de pression sur la mmoire qui risque dimpacter fortement les performances.

MSSQL:SQL Errors\Erros/sec
Signe derreurs, par exemple aprs modification de structure. Les erreurs sont examiner laide dune trace SQL.

MSSQL:SQL Statistics\Batch requests/sec


Nombre de requtes SQL envoyes au serveur. Indique la demande de travail laquelle votre serveur est soumis.

MSSQL:SQL Statistics\SQL Compilations/sec


Nombre de compilations de plans par seconde. Une valeur leve peut indiquer un manque de mmoire pour le cache de plans, ou un besoin de configurer lauto-paramtrage plus agressivement (voir section 9.2).

MSSQL:SQL Statistics\SQL Re-Compilations/sec


Nombre de recompilations par seconde. Une valeur leve indique un problme de performance des requtes. Identifiez les procdures incrimines laide du profiler, et appliquez les solutions proposes dans la section 9.1.2.

124

Chapitre 5. Analyse des performances

Network Interface\Current Bandwidth et Network Interface\Bytes Total/sec


Indique la capacit de votre connexion rseau, et son dbit actuel, respectivement. La comparaison des deux donne lutilisation actuelle (attention aux diffrences dchelle dans laffichage en graphe du moniteur de performance).

PhysicalDisk\Split IO/sec
Requtes dentres/sorties que Windows doit diviser pour honorer. Sur un disque simple, un nombre lev est un indicateur de fragmentation. Attention, nen tenez pas compte sur un systme RAID, il est normal dy trouver des splits.

Au sujet des compteurs de disque Lobjet PhysicalDisk fournit des compteurs par disque physique, et lobjet LogicalDisk par partition (lettre de lecteur). Les objets de compteurs de disque ont un effet sur les performances du serveur, mme lorsquon ne les affiche pas avec le Moniteur Systme. Pour cette raison, sur Windows NT ces compteurs sont totalement dsactivs, et sur Windows 2000, seuls les compteurs physiques (PhysicalDisk) sont activs (donc lobjet LogicalDisk tait dsactiv). Un outil en ligne de commande, nomm diskperf, est disposition pour activer ou dsactiver ces compteurs. partir de Windows Server 2003, ces compteurs sont activs par dfaut, leur impact ntant pas rellement significatif sur les machines modernes. diskperf est toujours prsent si vous voulez dsactiver une partie des compteurs. Sachez toutefois quune activation ou dsactivation ncessite un redmarrage de la machine, ce qui rend son utilisation peu intressante

Process\% Processor Time


Ce compteur donne le temps CPU total utilis par un processus. Cest un cumul des compteurs % Privileged Time et % User Time du mme objet.

% Privileged Time
Cest le pourcentage de temps CPU utilis par le processus en mode privilgi. Le temps privilgi (privileged time, ou kernel time), est le temps dactivit des CPU ddis aux appels systme (gestion de la mmoire, entres/sorties, etc.). Cest souvent le cas dun service Windows qui a besoin daccder aux donnes prives du systme, qui sont protges contre des accs de processus sexcutant en mode utilisateur. Les appels implicites au noyau, comme les fautes de page, sont aussi compts comme du temps privilgi. Sur une machine ddie SQL Server, le pourcentage de temps privilgi devrait tre faible, SQL Server excutant la majeure partie de son travail en temps utilisateur. Vous pouvez obtenir une vision rapide du ratio temps privilgi / temps utilisateur laide du gestionnaire de tches (Windows task manager). Longlet performance montre notamment des graphes de CPU. Vous pouvez afficher la partie de temps privilgi (appele ici kernel time), en activant dans le menu View loption show kernel times (figure 5.14).

5.4 Choix des compteurs

125

Figure 5.14 Affichage du temps privilgi

System\Context Switches/sec
Indique le nombre de changements de contexte la seconde. Dans un environnement multiprocesseurs, un nombre de threads plus important que le nombre de processeurs sexcute en multitche premptif1. Le systme dexploitation est responsable de lattribution des temps dutilisation processeur chaque thread. Il organise donc une rpartition dexcution, comme un animateur de dbat tlvis qui distribue les temps de parole tout au long de lmission. Chaque thread sexcute dans un contexte, son tat est maintenu, notamment dans les registres du processeur. Passer dun thread lautre implique donc de remplacer le contexte du thread remis dans la pile, par le contexte du thread qui lOS redonne le processeur. Nous avons vu que SQL Server implmente une couche appele SQLOS, qui est un environnement semblable un systme dexploitation en miniature. Cet environnement gre ses propres threads, appels worker threads , mapps par dfaut des threads Windows. Un rservoir (pool) de worker threads est maintenu par SQLOS pour excuter les tches du serveur SQL. Le nombre de threads dans ce pool est dter1. En multitche premptif, un temps dfini est attribu par le systme dexploitation chaque processus pour excuter sa tche. Si le travail nest pas fini lexpiration de ce dlai, le processus est renvoy dans la pile et laisse sa place au processus suivant dans la file dattente, et ainsi de suite.

126

Chapitre 5. Analyse des performances

min par loption de configuration du serveur max worker threads . La valeur par dfaut de cette option est 0, qui correspond une auto-configuration de SQL Server. Cette valeur est optimale dans la grande majorit des cas. Si vous avez effectu une mise jour directe dune instance SQL Server 2000 vers 2005, vrifiez que cette valeur est bien 0. En effet, en SQL Server 2000, la valeur par dfaut tait 255, et elle demeure lors de la mise jour. Lauto-configuration en SQL Server 2005 suit une logique relative au nombre de processeurs, explique dans les BOL sous lentre max worker threads Option . Pour connatre la valeur actuelle sur votre systme :
SELECT max_workers_count FROM sys.dm_os_sys_info

Il est hasardeux daugmenter le nombre de worker threads, et en gnral inutile, voire contre performant1. Cette option nest pas une option intressante pour augmenter les performances. Donc, les changements de contexte sont coteux, mais ncessaires. Des valeurs comme 10 000 ou 15 000 sont normales. Une augmentation exagre de context switches devient un problme, car les processeurs passent trop de leur temps changer de contexte. Dans ce cas, une augmentation du nombre de processeurs est envisager. Dans un systme en architecture SMP qui comporte beaucoup de processeurs, o le nombre de changements de contexte est lev de faon consistante, et o les processeurs sont en utilisation presque maximum en permanence, vous pouvez essayer de configurer SQL Server pour lutilisation de fibres. Les fibres sont des threads plus lgers, qui peuvent changer de contexte sans quitter le mode utilisateur. Aprs activation de ce mode, SQLOS mappe ses worker threads des fibres et non plus des threads Windows. Passer du mode thread au mode fibre se fait en changeant loption de serveur lightweight pooling , comme ceci :
EXEC sp_configure 'show advanced options', 1 RECONFIGURE EXEC sp_configure 'lightweight pooling', 1 RECONFIGURE EXEC sp_configure 'show advanced options', 0 RECONFIGURE

Ce changement nest pas faire la lgre, car il peut aussi avoir un effet ngatif. En tout cas, le gain a peu de chances dtre spectaculaire. Notez galement que SQL Mail nest pas disponible en mode fibre (ce qui a peu dimpact, car vous lavez certainement remplac par loutil Database Mail), ni le support des objets de code .NET. Un compteur Thread\Context Switches/sec vous permet aussi dentrer dans les dtails : quels threads sont beaucoup dplacs.

1. Comme nous lexplique Ken Henderson dans cet article de blog : http://blogs.msdn.com/ khen1234/archive/2005/11/07/489778.aspx

5.4 Choix des compteurs

127

System\Processor Queue Length


Indique le nombre de threads qui sont dans la file dattente du processeur. Ce sont donc des threads qui attendent que le systme dexploitation leur donne accs un processeur. Il y a une seule file dattente, quel que soit le nombre de processeurs. En divisant ce nombre par la quantit de processeurs, vous avez une ide du nombre de threads en attente sur chacun. Au-dessus de 10 threads en attente par processeur, commencez vous inquiter.

5.4.3 Compteurs pour tempdb


MSSQL:Access Methods\Worktables Created/sec
nombre de tables de travail cres par seconde. Les tables de travail sont utilises par le moteur dexcution de SQL Server pour rsoudre des plans de requte contenant du spooling. Ce nombre devrait rester infrieur 200. Les moyens dviter les tables de travail sont damliorer la syntaxe des requtes (et dviter les requtes sans clause WHERE, qui doivent traiter trop de lignes), de diminuer lutilisation des curseurs ou des objets larges (LOB), et de crer les index ncessaires aux tris prliminaires dans les requtes.

MSSQL:Access Methods\Workfiles Created/sec


Nombre de fichiers de travail crs par seconde. Les fichiers de travail sont utiliss par le moteur dexcution dans des plans de requte utilisant le hachage. Pour diminuer les fichiers de travail, vitez le traitement de jeux de rsultats trop importants : le hachage est utilis dans les jointures et les regroupements qui doivent traiter un nombre important de tuples.

MSSQL:General Statistics\Temp Tables Creation Rate


Le nombre de tables temporaires et variables de type tables cres par seconde. Utile pour diffrencier, dans une utilisation importante de tempdb, ce qui revient la cration de tables temporaires utilisateurs par rapport aux dpts de versions et aux fichiers et tables de travail.

MSSQL:Transactions\Free Space in tempdb (KB)


Utile pour dtecter un manque despace libre dans tempdb. Une bonne ide est de crer une alerte de lagent SQL sur un dpassement de cette valeur.

MSSQL:Transactions\Version Store Size (KB)


Indique la taille des dpts de versions pour les versions de ligne (row versioning). Cette valeur devrait voluer dynamiquement au fur et mesure de lutilisation du row versioning, et donc diminuer quand les besoins de versions diminuent. Une taille de dpt de versions qui continue daugmenter indique un problme, probablement une transaction ouverte trop longtemps, empchant la libration des versions de ligne.

128

Chapitre 5. Analyse des performances

MSSQL:Transactions\Version Generation Rate (KB/s) et MSSQL:Transactions\Version Cleanup Rate (KB/s)


Indiquent le nombre de Ko de dpts de versions crs et supprims par seconde. Des valeurs importantes indiquent une utilisation importante du row versioning, mais si les compteurs gnraux du systme (disque, mmoire, CPU) ne montrent pas de pression excessive, cest le signe dun fonctionnement de tempdb non bloquant, ce dont on peut se fliciter. Par contre, la corrlation avec une augmentation de la pression sur le systme, peut nous donner des indications de sa cause.

5.4.4 Compteurs utilisateur


Lobjet de compteurs MSSQL:User Settable permet de rcuprer des compteurs gnrs explicitement dans votre code. Utilisez-le pour afficher des statistiques sur des oprations dont vous voulez connatre la frquence dutilisation, ou le volume. Vous pouvez par exemple renvoyer un nombre de lignes affectes par un traitement, ou le nombre de lignes dans une table. Vous disposez de dix compteurs, numrots de 1 10. Vous devez mettre jour la valeur du compteur, en excutant la procdure stocke sp_user_counterX, o X est le numro du compteur. Par exemple, pour passer le nombre de lignes affectes par la dernire instruction de votre procdure au compteur 1, vous cririez :
EXEC sp_user_counter1 @@ROWCOUNT

Cette valeur sera attribue au compteur, qui la restituera tant que vous nexcuterez pas sp_user_counter1 nouveau. Ainsi, pour afficher le nombre de lignes dune table (sa cardinalit), vous pouvez crer un dclencheur sur INSERT et DELETE, qui excute cette procdure.

5.4.5 Identification de la session coupable


Lorsque vous constatez une augmentation anormale de lactivit CPU, vous souhaitez sans doute entrer dans les dtails et identifier la session SQL Server responsable. Cest possible en corrlant lidentifiant de thread Windows et le kpid des informations de session SQL Server. Le dsavantage de cette opration est son ct fastidieux, et surtout sa dure : il se peut quune fois la premire partie de lopration effectue, la session ait dj termin son travail, et donc quon arrive trop tard pour recueillir les informations ncessaires. Vous devez premirement trouver lidentifiant du thread qui consomme un niveau important de CPU. Vous disposez pour cela des compteurs de performances Thread : % Processor Time et Thread : ID Thread. Slectionnez toutes les instances Sqlservr. Affichez le rsultat en mode rapport et cherchez le thread consommateur, comme indiqu en figure 5.15.

5.4 Choix des compteurs

129

Figure 5.15 Recherche des threads

Lorsque le thread est identifi, vous trouvez la correspondance dans SQL Server partir de la colonne kpid de la vue sys.sysprocesses (une vue qui correspond lancienne table systme du mme nom), qui reprsente le mme identifiant :
SELECT * FROM sys.sysprocesses WHERE kpid = ID_Thread;

5.4.6 Utiliser les compteurs dans le code SQL


La vue systme sys.dm_os_performance_counters permet de retrouver dans du code SQL des valeurs de compteurs de performance. Elle remplace la table master.dbo.sysperfinfo de SQL Server 2000. Cette table est toujours disponible, mais elle est dprcie. Il est donc recommand dutiliser la vue de gestion dynamique pour la remplacer. Cette vue ne permet de retrouver que les compteurs propres aux objets SQL Server, et pas les compteurs des objets systme. Vous pouvez ainsi, dans votre code, requter ou ragir aux valeurs de compteur. Voici par exemple une requte qui vous indique quel est le pourcentage du journal de transaction rempli, pour chaque base de donnes :
SELECT instance_name, cntr_value as LogFileUsedSize FROM sys.dm_os_performance_counters WHERE counter_name = 'Log File(s) Used Size (KB)';

130

Chapitre 5. Analyse des performances

5.5 PROGRAMMATION DE JOURNAUX DE COMPTEURS


Le moniteur de performances ne permet pas seulement de suivre en temps rel lactivit du serveur, il est aussi capable denregistrer cette activit dans des fichiers, afin de conserver une trace rechargeable ensuite dans le mme outil pour obtenir une vue sur une longue priode. Cest bien sr loutil de base pour tablir la situation de rfrence ou baseline dont nous avons dj parl. Pour ouvrir un fichier de journal de compteur, lancez le moniteur systme et, au lieu de suivre les compteurs en temps rel, choisissez de lire les donnes dun fichier. Vous avez un bouton sur la barre doutil, en forme de cylindre, qui vous permet de charger le fichier. Combinaisons de touches : CTRL+T pour voir lactivit du serveur en temps rel, CTRL+L pour ouvrir un fichier de journal. Pour crer un journal de compteurs, un dossier nomm performance logs and alerts , comportant une entre nomme counter logs est visible dans larborescence de la console (figure 5.16).

Figure 5.16 Traces de performance

Vous voyez sur la figure 5.16 une entre Trace logs , qui correspond aux traces gnres par larchitecture ETW (Event Tracing for Windows). Cette partie est utile en SQL Server 2008 pour grer des sessions dvnements tendus (voir plus loin section 5.3.5).

5.5 Programmation de journaux de compteurs

131

Par un clic droit sur lentre counter logs , ou dans la fentre de droite, vous pouvez crer un nouveau journal en slectionnant new log settings dans le menu. La premire tape consiste donner un nom votre dfinition de journal, puis slectionner les compteurs dsirs, en cliquant sur le bouton Add counters , dans la fentre reproduite en figure 5.17.

Figure 5.17 Premier onglet de la configuration de trace de performances

Vous ajoutez ensuite vos compteurs comme dans le moniteur systme. Choisissez dans cette mme fentre lintervalle dchantillonnage (sample data every). Un chantillonnage trop fin augmente exagrment la taille des fichiers de journal, et impacte les performances du serveur. Inversement, un intervalle trop important risque de ne pas montrer assez prcisment les variations de compteurs et les pics brusques dactivit. Essayez avec lintervalle qui vous convient. De dix quinze secondes peut tre une bonne valeur de dpart. La fentre de configuration de la dfinition du journal a trois onglets. Le deuxime permet de configurer la destination (figure 5.18).

132

Chapitre 5. Analyse des performances

Figure 5.18 Deuxime onglet de la configuration de trace de performances

Vous pouvez enregistrer les informations de compteurs dans un ou plusieurs fichiers texte ou binaire, ou dans une table SQL. Comme pour le profiler, vitez denregistrer directement dans une base de donnes, pour dvidentes raisons de performances. Le fichier binaire sera le plus rapide et le plus compact. Choisissez un fichier texte si vous souhaitez importer ensuite votre journal dans une table SQL pour linspecter par requtes, ou si vous importez les rsultats dans un outil de supervision ou un gnrateur de graphes. Vous pouvez limiter la taille du fichier, ou programmer une rotation.

Outils en ligne de commande Les journaux de performance et les sessions dvnements ETW peuvent tre grs en ligne de commande, grce lexcutable logman.exe. Vous pouvez crer, lancer et arrter des traces. Par exemple, pour dmarrer et arrter un journal de compteur nomm sqlcounters cr dans linterface :

logman start sqlcounters logman stop sqlcounters

5.6 Programmation dalertes

133

Les fichiers gnrs par le journal peuvent tre facilement convertis entre des formats texte, binaire, et une table SQL, laide de lexcutable relog.exe, livr avec Windows.

5.6 PROGRAMMATION DALERTES


Le moniteur systme permet galement de programmer des alertes, un peu dans lesprit de lagent SQL. Vous pouvez utiliser indistinctement lun ou lautre outil pour programmer un envoi de notification lorsquun compteur atteint un certain seuil, mais aussi ce qui peut se rvler trs pratique pour dclencher une collecte dinformation spcifique. Imaginez que vous vouliez dmarrer une trace SQL et un journal de compteurs ds que lactivit des processeurs atteint 90 %, afin dobtenir les donnes au bon moment, cest--dire quand la monte en charge se produit. Vous pourriez programmer une alerte dans lagent SQL, mais vous pouvez aussi le faire dans le moniteur systme. Les actions dclenches seront les mmes, voyons-le avec le moniteur systme. Dans le dossier Performance logs and alerts du moniteur systme, faites un clic droit sur le sous-dossier Alerts , et choisissez New Alerts Settings . Aprs avoir choisi un nom parlant, slectionnez un compteur de performance avec le bouton Add . Pour cet exemple, nous prendrons Processor(_Total)\% Processor Time. Le deuxime onglet, Action , vous permet de ragir lalerte. Nous allons utiliser la possibilit de dmarrer un journal de compteurs, et de lancer une commande du systme. Ainsi, nous pouvons lancer, ds que le besoin sen fait sentir, un journal de compteurs. Vous pouvez aussi, comme nous lavons vu, planifier le journal de compteurs pour sarrter aprs une certaine dure, ce qui vous permettrait par exemple de conserver dans des fichiers numrots squentiellement, des traces de trente minutes, ds que la charge des processeurs atteint 90 %. En utilisant lexcution dune commande, vous pouvez en mme temps dmarrer une trace. Le plus simple serait, laide dune commande SQLCMD, de lancer une trace stocke sur le serveur. Nous avons vu la procdure sp_trace_setstatus dans la section 5.2.2. Dans le dernier onglet, montr en figure 5.19, vous indiquez les plages de temps pendant lesquels lalerte sera active, cest--dire durant quelle priode elle peut se dclencher.

134

Chapitre 5. Analyse des performances

Figure 5.19 Raction lalerte de performances

5.7 CORRLATION DES COMPTEURS DE PERFORMANCE ET DES TRACES


Nous avons vu que vous pouvez enregistrer aussi bien les traces SQL que les journaux de compteur. Si vous collectez les deux jeux dinformations, vous pouvez utiliser linterface du profiler pour corrler les informations dans un affichage regroup, trs utile pour identifier les causes de surcharge. Lorsque vous disposez de vos fichiers enregistrs, le journal de compteurs et le fichier de trace, ouvrez dabord ce dernier dans le profiler. Vous verrez, ensuite, dans le menu Fichier, la commande Importez les donnes de performance . Slectionnez le fichier de journal de compteur, choisissez les compteurs enregistrs que vous voulez voir apparatre (figure 5.20). Vous verrez ensuite dans le profiler les deux fentres lune sous lautre : les vnements de trace, puis les graphes de performance (figure 5.21).

5.7 Corrlation des compteurs de performance et des traces

135

Figure 5.20 Choix des compteurs pour la corrlation

Figure 5.21 Rsultat de la corrlation

136

Chapitre 5. Analyse des performances

Vous pouvez ensuite vous situer sur une ligne dvnement, ou un moment du graphe, et laffichage de lautre fentre sera automatiquement positionn sur la priode correspondante. Cela permettra une analyse dtaille de limpact de vos requtes sur le serveur. Cette fonctionnalit nest possible quavec des traces et des journaux de compteurs sauvegards pralablement, la corrlation en temps rel nest pas possible.

5.8 VNEMENTS TENDUS (SQL SERVER 2008)


SQL Server 2008 inclut une nouvelle architecture de gestion dvnements, appele vnements tendus (extended events, ou XEvents), fonds sur larchitecture de Event Tracing for Windows (ETW). Elle permet de dfinir des vnements rcuprables par plusieurs clients, de faon synchrone ou asynchrone1. Au moment o nous rdigeons ce livre, SQL Server 2008 est toujours en version bta et tous les vnements ne sont pas implments. La puissance de XEvents est bien suprieure aux mthodes de trace actuelle. Nous allons en parcourir les fonctionnalits.

5.8.1 Architecture
Les fournisseurs de ces vnements sont organiss en packages, de simples containers, comme des espaces de noms, identifis par un nom et un GUID, qui contiennent divers objets : events : des vnements dclenchs, ils correspondent principalement aux compteurs de performance SQL Server et aux vnements de trace SQL. actions : des actions attribues dynamiquement au dclenchement dun vnement, elles peuvent tre utiles pour ajouter des informations supplmentaires aux vnements( par exemple le SPID, le code SQL de linstruction, etc.). targets : des consommateurs dvnements. SQL Server en implmentent plusieurs, soit pour crire les vnements dans un fichier, soit pour les traiter en mmoire (accessible par des vues de gestion dynamique). Par exemple, la destination Event pairing matche les couples dvnements, et permet didentifier des vnements non termins (transaction non valide, verrou non libr, etc.). types : indiquent les formats dvnement pour assurer leur prise en main dans le processus de dclenchement (notamment leur mise en buffer).

1. Un fournisseur ETW est aussi prsent en SQL Server 2005. Vous pouvez trouver des informations sur son fonctionnement dans cette entre de blog : http://blogs.msdn.com/sqlqueryprocessing/archive/2006/11/12/using-etw-for-sql-server-2005.aspx

5.8 vnements tendus (SQL Server 2008)

137

predicates : des clauses de filtre. Le filtrage se fait au plus prs de lvnement, ce qui vite de dclencher lvnement sil ne correspond pas au filtre. Les prdicats peuvent conserver leur tat, on peut donc par exemple compter les dclenchements dun vnement dans un prdicat, et utiliser ce compteur comme filtre. maps : des tableaux de rfrence qui permettent de lier des identifiants courts dans lvnement, des correspondances claires dans la map. Cela permet, comme dans un modle relationnel, de diminuer la taille des donnes dvnement. Les vnements sont classs en channels (sur le modle de ETW). Les vnements faisant partie du channel Analytic sont ceux qui seront principalement intressants pour lanalyse de performances. Ils contiennent des vnements correspondant aux traces SQL et aux compteurs de performances fournis par SQL Server (avec beaucoup plus dinformations quune simple valeur de compteur). Les colonnes keyword et channel de chaque vnement permettent den retrouver le package et le channel, et de les classer. La colonne keyword correspond une information claire tire dun lien sur une map.
SELECT map_value as Keyword FROM sys.dm_xe_map_values WHERE name = 'keyword_map';

Vous trouvez une requte permettant de lister les vnements disponibles dans lentre des BOL SQL Server 2008 nomme How to: View the Events for Registered Packages . Le cot de dclenchement des vnements tendus a t optimis autant que possible, et est lger (Microsoft dclare 2 microsecondes de processeur cadenc 2 GHz). Les vnements asynchrones sont prfrer, car ils ont un impact plus faible sur les performances du serveur. Les vnements asynchrones sont grs par des buffers, des parties de mmoire qui stockent les vnements avant de les envoyer aux clients qui en font la demande ou de les crire sur le disque. Cette deuxime partie sera effectue par un thread en background, ce qui est moins coteux.

5.8.2 Cration dune session


Pour rcuprer des vnements tendus, vous devez dabord crer une session, qui est la dclaration nomme dun groupe dvnements tracs. Ces sessions tant enregistres dans des tables systmes, elles existent donc encore aprs un redmarrage de linstance, et elles peuvent tre configures pour redmarrer automatiquement avec elle. Une session se cre avec la commande CREATE EVENT SESSION, dans laquelle nous indiquons les vnements rcuprer (ADD EVENT nom_du_package.evenement), les destinataires (ADD TARGET), etc. :
CREATE EVENT SESSION masession

138

Chapitre 5. Analyse des performances

ON SERVER ADD EVENT sqlos.async_io_requested ADD EVENT sqlos.async_io_completed ADD EVENT sqlserver.database_transaction_begin ADD EVENT sqlserver.database_transaction_end ADD TARGET package0.etw_classic_sync_target (SET default_etw_session_logfile_path = N'C:\temp\sqletw.etl');

Pour dmarrer la session :


CREATE EVENT SESSION masession ON SERVER STATE = start;

Vous ne pouvez crer la session et la dmarrer en mme temps, un peu comme avec les procdures stockes de dfinition de SQL Trace. Les deux oprations ne peuvent en effet tre simultanment transactionnelles : on ne peut pas annuler la cration des mtadonnes et lenvoi des vnements en mme temps, puisquil est impossible de dsenvoyer les vnements. Les sessions peuvent tre requtes dans les vues systmes suivantes : sys.server_event_sessions sys.server_event_session_actions sys.server_event_session_events sys.server_event_session_fields sys.server_event_session_targets

Pour amliorer les performances de la session, permettez le vidage de buffers en cas de manque, ceci en activant loption ALLOW SINGLE EVENT LOSS, ou ALLOW MULTIPLE EVENT LOSS. Le nombre dvnements perdus est indiqu avec loption ALLOW SINGLE EVENT LOSS. Loption ALLOW MULTIPLE EVENT LOSS libre en revanche la totalit dun buffer, qui peut contenir un grand nombre dvnements, et rend impossible le retour du nombre dvnements perdus. Dans la plupart des cas, loption ALLOW SINGLE EVENT LOSS est recommande, car elle offre de bonnes conditions de performances.

NO_EVENT_LOSS Certains vnements bas niveau ne peuvent tre paramtrs avec NO_EVENT_LOSS, car lattente sur la libration de buffer serait trop handicapante pour le systme. Cest le cas dvnements comme FILE_IO ou CONTEXT_SWITCH. Dans la grande majorit des cas, ALLOW_SINGLE_EVENT_LOSS est parfait pour rcuprer des informations utiles.

Toute larchitecture des vnements tendus est visible travers des vues de gestion dynamique prfixes par sys.dm_xe_. La vraie question, au moment o nous crivons ce livre, est : que faire des vnements dclenchs ? Deux solutions :

5.9 Outils de supervision

139

Enregistrer le rsultat de la session dans un fichier .etl, laide du destinataire etw_classic_sync_target comme nous lavons vu dans notre exemple de code. Ce fichier .etl en format binaire est importable directement dans les outils xperf (voir section 5.4) sur Vista ou Windows Server 2008. Avec Windows Server 2003, lexcutable tracerpt.exe (install avec Windows, dans system32), permet de transformer le contenu du fichier au format CSV, qui peut tre ensuite import dans votre outil de supervision. Rcuprer les vnements directement dans la vue de gestion dynamique sys.dm_xe_session_targets, laide des destinataires asynchronous_bucketizer (criture asynchrone dans un cache mmoire) et pair_matching. Vous trouverez un exemple de destinataire pair_matching, qui permet de tracer des vnements lis, comme les blocages de processus, dans les BOL, sous lentre How to: Determine Which Queries Are Holding Locks .

5.9 OUTILS DE SUPERVISION


Un certain nombre doutils, quils soient livrs par Microsoft ou non, sont utiles pour tracer les performances ou des informations prcises du systme. Dans cette section, nous allons rapidement en lister quelques-uns, afin de vous encourager les dcouvrir et les utiliser.

Windows Performance Toolkit


Le Windows Performance Toolkit, aussi appel Xperf, est un outil disponible pour Vista et Windows Server 2008, compos principalement de deux excutables : xperf.exe, qui permet des captures de trace, et xperfview.exe, qui les affiche. xperf.exe est fond sur ETW (Event Tracing for Windows), que vous pouvez activer dans le traditionnel moniteur de performances (perfmon) via le nud Performance Log and Events / Trace Logs . Son avantage est de pouvoir corrler des traces de format ETW, par exemple des traces du systme, et des traces des vnements tendus de SQL Server 2008. Il est tlchargeable sur le site de Microsoft ladresse http://www.microsoft.com/ whdc/system/sysperf/perftools.mspx. Lenregistrement de trace est possible sur Windows Server 2003 en copiant la main lexcutable xperf.exe, mais le processing du rsultat de la trace nest possible que sur Vista ou Windows Server 2008.

SQLDIAG
SQLDIAG est un excutable install avec SQL Server, dont le but est de collecter le plus possible dinformations du systme, normalement pour les envoyer aux quipes support de Microsoft. Il rcupre des donnes du systme et de SQL Server, la demande (prenant un snapshot) ou de faon continue. Il peut tre invoqu en ligne de commande, ou install comme un service. Il est capable de rcuprer ses informations dinstances SQL Server en cluster. Il enregistre son rsultat dans des

140

Chapitre 5. Analyse des performances

fichiers texte dans un rpertoire spcifique. Il peut tre configur en ligne de commande, ou par un fichier de configuration. Il peut rcuprer des journaux de performance Windows, des traces SQL, les journaux dvnement Windows, les informations de configuration du systme et de SQL Server, les informations de blocage de sessions, etc. Cest donc un outil extrmement intressant pour le diagnostic de problmes. Vous trouverez plus dinformations dans les BOL, sous lentre SQLdiag Utility .

SQLNexus
SQLNexus est un outil dvelopp par Bart Duncan et Ken Henderson, qui simplifie lutilisation de SQLDIAG, tout dabord en fournissant un fichier de configuration pour SQLDIAG et des scripts pour obtenir des informations supplmentaires des vues de gestion dynamique, ensuite en stockant les output dans une base de donnes, et en offrant des rapports Reporting Services trs complets pour analyser les rsultats. Vous pouvez le tlcharger sur CodePlex : http://www.codeplex.com/sqlnexus

Log Parser
Log Parser nest pas spcifiquement destin SQL Server. Cest un outil trs puissant de lecture, de recherche et de reporting qui travaille sur tous types de fichiers journaux (fichiers log, XML, CSV) et dans les journaux dvnement de Windows, la base de registre, etc. Vous pouvez lire, chercher avec un langage de requte de type SQL, formater le rsultat en texte ou en HTML, et mme gnrer des graphes. Vous pouvez tlcharger Log Parser ici : http://www.microsoft.com/technet/scriptcenter/tools/logparser/. Une interface graphique libre de droits a t cre par un programmeur, vous la trouvez ici : http://www.lizardl.com/.1

Windows Sysinternals
Les outils Sysinternals, dvelopps par Mark Russinovich (un expert reconnu des mandres de Windows) et Bryce Cogswell, sont maintenant disponibles sur le site de Microsoft, depuis que Winternals, la socit de Russinovich, a t rachete en 2006. Vous trouverez ladresse http://technet.microsoft.com/en-us/sysinternals/ des outils de diagnostic systme indispensables et simples dutilisation. Parmi eux : DiskMon : trace lactivit disque. Process Monitor : affiche en temps rel lactivit des processus et threads, de la base de registre et des activits disque. Process Explorer : affiche en dtail lactivit des processus : handles, DLL utilises, fichiers ouverts, etc. Trs utile pour entrer dans les dtails. TCPView : affiche en temps rel les ports TCP et UDP ouverts. Un quivalent graphique de netstat.

1. Sachez galement quun livre est publi sur cet outil : Microsoft Log Parser Toolkit, chez Syngress.

5.9 Outils de supervision

141

SQL Server 2005 Performance Dashboard Reports


Le SQL Server 2005 Performance Dashboard est une collection de rapports Reporting Services requtant les vues de gestion dynamique. Elle est tlchargeable sur le site de Microsoft, sous forme de fichier dinstallation .msi. Aprs installation, vous disposez des fichiers de dfinition de rapports .rdl, dans le rpertoire choisi linstallation, ainsi quun fichier de script setup.sql, excuter avant toute chose. Ce script cre quelques objets dans msdb, sans plus. Tous les rapports sont bass sur des DMV. Ensuite, pour afficher les rapports dans SSMS, vous pouvez utiliser la fonctionnalit de rendu de rapports locaux (Reporting Services nest donc pas requis). Pour ajouter un rapport local, choisissez Custom Reports dans le menu affich en figure 5.22.

Figure 5.22 Choix des rapports

Allez chercher ensuite le rapport performance_dashboard_main.rdl dans le rpertoire dinstallation (figure 5.23). Le rapport saffiche dans une nouvelle fentre (figure 5.24).

142

Chapitre 5. Analyse des performances

Figure 5.23 Rapport performance_dashboard_main

Figure 5.24 Rapport principal

5.9 Outils de supervision

143

Ce rapport donne des liens sur tous les autres, cest donc votre point dentre sur les informations de charge, les requtes les plus coteuses, etc. Une fois choisi, il continuera apparatre dans le menu Reports .

Server Performance Advisor (SPA)


De son nom complet, Microsoft Windows Server 2003 Performance Advisor , SPA est un outil graphique qui simplifie la collecte de donnes de performances et offre une vue commente des performances gnrales du systme, avec des conseils. Vous pouvez le tlcharger sur le site de Microsoft, avec une recherche sur son nom complet. Un autre outil de ce type existe, nomm PAL (Performance Analysis of Logs). Il sagit dun script VBScript hberg sur CodePlex (le serveur de dveloppements communautaires tournant autour des produits Microsoft), qui avale un journal de compteurs (voir section 5.3.2) et produit un rsultat en tableau HTML indiquant si les valeurs sont en de ou au-del des limites conseilles par Microsoft. Site de loutil : http://www.codeplex.com/PAL/.

Management Data Warehouse (SQL Server 2008)


SQL Server 2008 intgre un outil de centralisation des donnes de performances, appel Management Data Warehouse , qui permet, travers des lots Integration Services crs par assistant, de centraliser des collections donnes (data collections) de gestion et de performance bases sur les traces, des compteurs de performances, les vues de gestion dynamique, etc., dans un entrept centralis (une base de donnes) qui peut collecter les donnes de plusieurs instances SQL. Des rapports Reporting Services locaux sont ensuite disponibles pour consulter ces statistiques. Cest un outil intressant de surveillance centralise de vos instances SQL.

DEUXIME PARTIE

Optimisation des requtes


Le travail doptimisation sur un serveur de production est avant tout un travail de modlisation logique et physique des donnes et damlioration de la qualit du code SQL. Mme les machines les plus puissantes peinent excuter des requtes mal crites, et doivent attendre lorsque des verrous sont poss sur les donnes dsires. Cest au niveau du code que les problmes se posent en gnral, et cest l o leffet de levier est le plus important. Ce nest que lorsquon a la garantie que le code et le schma sont optimiss que la mise jour matrielle doit tre envisage. Nous allons donc passer du temps sur ce sujet essentiel.

6
Utilisation des index

Objectif
Vous nignorez pas que lindexation est un sujet important. Sans index, les SGBDR nauraient aucune utilit, car ils ne pourraient permettre aucune recherche rapide dans les donnes. Toute manipulation dans les tables obligerait le moteur les parcourir de bout en bout. Mme une simple contrainte dunicit serait irralisable. Comprendre et utiliser les index au maximum de leurs possibilits est ce qui va vous donner le levier le plus important pour augmenter les performances de vos bases de donnes. Nous allons donc les voir en dtail.

6.1 PRINCIPES DE LINDEXATION


Vous tes administrateur de bases de donnes, votre vie professionnelle est donc bien remplie. Mais cela ne soulage pas des grandes questions mtaphysiques : quel est le sens de la vie ? Pour y rpondre, vous dcidez de rencontrer un philosophe. Quallez-vous faire pour en trouver un dans votre ville ? Bien entendu, vous allez consulter lannuaire tlphonique, et plus prcisment les pages jaunes. Si vous ne disposez pas dannuaire, vous navez dautre solution que derrer dans les rues, pour y chercher au hasard, sur une plaque ou une bote aux lettres, la mention du mtier de philosophe. SQL Server nest pas diffrent. Lorsque vous filtrez le contenu dune table dans une requte, laide en gnral dun prdicat dans une clause WHERE, SQL Server doit parcourir la table pour valuer ce prdicat sur chaque ligne. Cette opration est videmment trs coteuse, proportionnellement la taille de la table. Toute la table doit tre mise en mmoire et parcourue, cest--dire que toutes les pages de

148

Chapitre 6. Utilisation des index

donnes qui contiennent la table sont charges, ce qui peut signifier des gigaoctets pour un simple SELECT Ce parcours est appel un scan. Le seul moyen dviter cela, est de crer un index. Un index est proprement une sorte de table des matires, contenant dans une structure arborescente toutes les valeurs prsentes dans une ou plusieurs colonnes, qui permet une recherche optimise sur les valeurs quelle contient. Cet arbre est appel arbre quilibr (balanced tree, ou B-Tree).

Figure 6.1 Arbre quilibr

La figure 6.1 montre une recherche travers larbre de lindex. Cette reprsentation en arbre est schmatique : lindex est physiquement stock dans des pages de donnes identiques celles qui abritent le contenu des tables. Nous verrons ce stockage un peu plus loin. Larbre B-Tree est constitu de nuds, ou niveaux, qui sont parcourus pour trouver linformation. La figure 6.2 montre un index quatre niveaux. Le premier nud, appel nud racine (root node), est le point dentre de la recherche. Dans notre cas, nous cherchons le mot philosophe . Nous sommes donc dirigs dabord vers la plage alphabtique comportant la lettre P. Au fur et mesure que nous descendons les nuds intermdiaires (intermediate node), le choix saffine, jusquau dernier niveau, le nud feuille (leaf node), o rside chaque occurrence de la cl, avec la rfrence des lignes de la table qui la contiennent. Ces rfrences se nomment des RID (Row ID), ce sont des combinaisons de numros de fichier, page et ligne, qui permettent au moteur de stockage de pointer sur la ligne. Cette recherche travers larborescence de lindex est reprsente dans le plan dexcution comme un seek. La rcupration du RID est appele un bookmark lookup, ou RID lookup. Vous en trouvez un exemple dans le plan dexcution graphique reproduit en figure 6.3.

6.1 Principes de lindexation

149

Figure 6.2 Nuds de lindex

Figure 6.3 Plan dexcution, seek et RID lookup

Vous voyez dans ce plan dexcution que RID lookup cote 57 % relativement au cot total de la requte. Il sagit donc dune opration lourde, nous reviendrons sur ce point. La recherche de chaque RID est exprime par un oprateur de boucle imbrique (nested loop). Cela veut simplement dire que pour RID trouv par le seek de lindex, il devra se produire une recherche de RID dans la table heap. Un peu comme les preuves de lmission tlvise Intervilles . Une quipe doit transporter des seaux deau travers un parcours difficile, mais chaque participant ne peut bien sr transporter que deux seaux la fois. Ils doivent donc constamment revenir au point de dpart pour prendre les seaux restants. Cest le nested loop : chaque apparition de nouveau match dans lindex, le moteur de requte drfrence le pointeur RID et va chercher physiquement la ou les lignes dans la page de donnes. Chaque niveau de lindex est une liste doublement lie entre les pages de ce niveau. En dautres termes, chaque page dindex contient les identifiants de la page

150

Chapitre 6. Utilisation des index

prcdente, et de la page suivante, du niveau. Cela permet SQL Server de parcourir (scan) le niveau dans lordre de la cl.

Scan avanc dition Entreprise Ldition Entreprise offre une optimisation avance des scans (Advanced Scanning) : lorsque plusieurs requtes doivent parcourir simultanment la mme table, ldition Entreprise peut partager ce parcours entre les processus. La fonctionnalit est appele parcours en mange (Merry-go-round scanning). Un processus se joint au parcours dj initi, et lorsque celui-ci est termin, il le poursuit sur les pages qui nont pas t parcourues depuis son entre dans le circuit. Cela peut fortement diminuer le temps de rponse de requtes qui doivent effectuer des scan importants, en partageant le travail et en diminuant lattente sur les verrous.

Contrairement dautres SGBD, qui implmentent des index de type bitmap, ou en hachage, il ny a pour linstant quune seule structure physique dindex en SQL Server : le B-Tree ( part lindex de texte intgral, un peu part). Mme les index XML, et les index spatiaux sur les donnes gographiques sont en interne des index de structure B-Tree, sur une table interne pour le XML, et sur une dcomposition hirarchique de lespace pour les index spatiaux. Lindex B-Tree peut tre de deux types : nonclustered et clustered (parfois appels non ordonn, et ordonn).

6.1.1 Index clustered


Nous avons vu que le niveau feuille de lindex pointe sur un RID, et que lopration de RID lookup permet de retrouver les lignes de la table mme. Cette structure, o lindex est physiquement spar de la table et o les cls sont lies aux lignes par un pointeur, est un index nonclustered. Un autre type dindex B-Tree, nomm clustered, rorganise physiquement les pages de donnes selon la cl de lindex. Commenons par une dmonstration.
USE tempdb GO CREATE TABLE dbo.indexdemo ( id int NOT NULL, texte char(100) NOT NULL DEFAULT (REPLICATE(CHAR(CEILING(RAND()*100)), 100)) ); GO INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT GO INTO INTO INTO INTO INTO INTO INTO INTO dbo.indexdemo dbo.indexdemo dbo.indexdemo dbo.indexdemo dbo.indexdemo dbo.indexdemo dbo.indexdemo dbo.indexdemo (id) (id) (id) (id) (id) (id) (id) (id) VALUES VALUES VALUES VALUES VALUES VALUES VALUES VALUES (1) (2) (3) (4) (5) (6) (7) (8)

6.1 Principes de lindexation

151

SELECT * FROM dbo.indexdemo GO

lissue de lexcution de ce code, nous obtenons un rsultat semblable ce qui est prsent figure 6.4.

Figure 6.4 Rsultat du SELECT

Les lignes apparaissent naturellement dans lordre dinsertion, qui correspond ici un ordre de nombres dans la colonne ID, simplement parce que nous avons insr les lignes avec un incrment de la valeur de ID. Si nous avions insr des ID dans le dsordre, ils seraient apparus dans le dsordre. Voyons maintenant ce qui se passe si nous supprimons, puis ajoutons une ligne :
DELETE FROM dbo.indexdemo WHERE id = 4 INSERT INTO dbo.indexdemo (id) VALUES (9) GO SELECT * FROM dbo.indexdemo GO

Rsultat sur la figure 6.5.

Figure 6.5 Rsultat du SELECT

152

Chapitre 6. Utilisation des index

Vous voyez que la ligne portant lID 9 est insre la place de la ligne supprime. La suppression a libr 104 octets dans la page, une insertion de la mme taille sest faite dans lespace laiss libre. Cela permet de remplir la page. Une table heap nest vraiment organise selon aucun ordre logique ou physique. Si nous voulons obtenir un rsultat ordonn, nous devons le spcifier dans la requte :
SELECT * FROM dbo.indexdemo ORDER BY id;

Ce qui ncessitera un tri dans le plan de la requte, comme nous le voyons dans le plan graphique, sur la figure 6.6.

Figure 6.6 Plan dexcution

Nous pouvons crer un index sur la colonne ID, pour permettre ce tri. Essayons :
CREATE NONCLUSTERED INDEX nix$dbo_indexdemo$id ON dbo.indexdemo (id ASC); GO SELECT * FROM dbo.indexdemo ORDER BY id;

Nous voyons la diffrence de plan dexcution sur la figure 6.7.

Figure 6.7 Plan dexcution

SQL Server parcourt le nud feuille de lindex, qui lui donne rapidement lordre par ID. Ensuite, il doit faire un RID lookup sur chaque ligne.

6.1 Principes de lindexation

153

Ici, lexemple donne un plan dexcution peu optimal. En effet, la boucle imbrique implique de faire une lecture par occurrence dans le parcours de lindex, donc de lire chaque fois une page. Mais, notre petite table tient tout entire dans une page de donnes. Donc, au lieu de lire une page, puis de trier, SQL Server fait dix lectures. ce volume de donnes, ce nest pas important, et SQL Server choisit un plan dexcution acceptable . Si la cardinalit de la table est plus importante, loptimiseur fera le choix dun scan, mme en prsence de lindex.

Supprimons cet index, et crons la place, un index clustered :


DROP INDEX nix$dbo_indexdemo$id ON dbo.indexdemo; GO CREATE CLUSTERED INDEX cix$dbo_indexdemo$id ON dbo.indexdemo (id ASC); GO

que donne un simple


SELECT * FROM dbo.indexdemo;

Nous voyons le rsultat sur la figure 6.8.

Figure 6.8 Rsultat du SELECT

Lordre des lignes a chang, sans mme que nous nayons spcifi une clause ORDER BY. Que sest-il pass ? Simplement, la cration de lindex clustered a rorganis les lignes de la table dans la page de donnes, selon lordre de la cl de lindex clustered. En dautres termes, la table est maintenant physiquement ordonne selon lindex clustered. Dsormais, toute ligne insre ou supprime sera place un endroit prcis dans lordre de la table. Si nous supprimons une ligne, en ajoutons une autre, nous verrons que, contrairement lexemple prcdent sur notre table heap, les lignes retournes par ce simple SELECT seront toujours dans lordre le lID, car cest lordre physique des lignes dans la table.

154

Chapitre 6. Utilisation des index

Attention Ce comportement ne vous ddouane pas de spcifier la clause ORDER BY si vous souhaitez rcuprer un jeu de rsultat dans un tri prcis. Vous navez aucune garantie que lordre des lignes sera consistant, mme sil existe un index clustered. Dans certains oprateurs de requte (les UNION, les jointures par exemple), SQL Server peut choisir de pr-trier un rsultat intermdiaire pour rendre les oprations suivantes plus rapides. De mme, le moteur de stockage peut retourner les pages dans un ordre favorable aux performances plutt que dans leur ordre logique .

Quen est-il de la requte avec ORDER BY id, et de son plan dexcution ? Rponse figure 6.9.

Figure 6.9 Plan dexcution

Plus besoin doprateur de tri : la table est dj dans lordre demand. Il suffit de faire un scan de lindex clustered. Mais, pourquoi navons-nous plus de RID lookup ? Comment SQL Server fait-il pour aller chercher la colonne [texte] que nous voulons afficher ? Utilisons DBCC IND pour observer la table, et lindex. Le dernier paramtre de DBCC IND indique lID de lindex. Un index clustered a toujours lID 1. Regardons donc ce que donne DBCC IND sur lindex 0 (la table elle-mme), et lindex 1 (lindex clustered) :
DBCC IND ('tempdb', 'dbo.indexdemo', 0); DBCC IND ('tempdb', 'dbo.indexdemo', 1);

Rsultat sur la figure 6.10.

Figure 6.10 Rsultat de DBCC IND

6.1 Principes de lindexation

155

Ici nous constatons plusieurs choses : le rsultat pour la table et pour lindex clustered est le mme. La page dindex et la page de table ont le mme ID, cest donc la mme. Nous voyons aussi que la page de donnes se trouve au niveau 0 (le nud feuille) de lindex clustered (1). Cela signifie donc que la page de donnes, et la page du niveau feuille de lindex clustered, est la mme. En effet, lorsque vous crez un index clustered, la table devient partie de lindex : le nud feuille de lindex clustered est la table elle-mme ! Il est important de comprendre ce fait. Une table de type heap, et une table clustered, sont deux objets bien diffrents. Lorsque SQL Server effectue un seek dans un index clustered, il ne quitte jamais lindex, et la notion de RID nexiste plus. Quand la recherche arrive au dernier niveau de lindex, elle est termine, car on se trouve dj au bon endroit : dans la page de donnes. Ainsi, contrairement un index nonclustered, ce qui se trouve dans le nud feuille nest pas uniquement la cl de lindex, mais aussi toutes les autres colonnes de la table. Cela implique galement que la table clustered comporte un ordre de pages. Comme la table est le dernier niveau de lindex, elle maintient comme les index une liste doublement lie de ses pages. Cela permet un scan de la table, pour retrouver des lignes dans lordre de la cl de lindex clustered. Une table heap, par contre, na aucune notion de page prcdente ou page suivante, puisque ses lignes ne sont organises dans aucun ordre explicite. Pour un heap, SQL Server sait quune page fait partie de la table, en inspectant les pages IAM de la table.

Bien quelle nait aucune particularit dun index, la table heap est elle aussi, en interne, considre comme un index : elle apparat dans la vue sys.indexes, comme un index de type heap, et ses pages sont alloues galement par des pages dIAM (Index Allocation Map).

Quen est-il des index nonclustered sur une table clustered ? Ils sont aussi dune autre espce. Observons leur diffrence fondamentale. Pour cela, nous allons ajouter un certain nombre de lignes dans notre table de dmo, et une troisime colonne que nous allons indexer :
DECLARE @i int SELECT @i = MAX(id)+1 FROM dbo.indexdemo; WHILE @i <= 2000 BEGIN INSERT INTO dbo.indexdemo (id) SELECT @i; SET @i = @i + 1 END ALTER TABLE dbo.indexdemo ADD petittexte CHAR(1) NULL; GO UPDATE dbo.indexdemo SET petittexte = CHAR(ASCII('a')+(id%26))

156

Chapitre 6. Utilisation des index

GO CREATE NONCLUSTERED INDEX nix$dbo_indexdemo$petittexte1 ON dbo.indexdemo (petittexte ASC); GO SELECT * FROM dbo.indexdemo WHERE petittexte = 'c';

Nous pouvons voir le plan dexcution de la dernire requte SELECT dans la figure 6.11.

Figure 6.11 Plan dexcution

Pourquoi avons-nous augment le nombre de ligne 2000 ? Sans cela, le plan dexcution choisi aurait t un scan de table. Le cot du lookup tant important, loptimiseur choisit plus volontiers un scan de la table si le nombre de pages est petit.

Ici, plus de RID lookup, mais un Key Lookup . La description de cet oprateur nous le dit clairement : cest lutilisation de la cl de lindex clustered pour effectuer la recherche dans la table. La conclusion est simple : lindex nonclustered, dans son nud feuille, ne fournit plus de RID pour la recherche, mais la cl de lindex clustered, de sorte que, dans ce cas, il y a en ralit un double seek dindex. Lorsquun seek est effectu dans un index nonclustered cr sur une table clustered, arriv au nud feuille de lindex nonclustered, la cl de lindex clustered est trouve, et ce dernier est travers son tour. Ceci est schmatis par la figure 6.12.

1. Lutilisation des $ dans le nommage des index et contraintes est une pratique personnelle, pas forcment conseille. SQL Server supporte le $ dans un identifiant dobjet, la norme SQL, non.

6.1 Principes de lindexation

157

Figure 6.12 Parcours du Key Lookup

Mais cest encore un peu plus que cela. La cl de lindex clustered est ajoute la fin de la cl de lindex nonclustered. Ce qui fait que, implicitement, lindex nonclustered est un index composite, contenant en plus la ou les cls de lindex clustered. Ce nest pas montr dans la dfinition de lindex, ni dans la vue systme sys.index_columns, mais si vous observez lindex, soit avec DBCC SHOW_STATISTICS (que nous verrons plus loin), soit avec DBCC PAGE, vous pouvez voir cela. Par exemple :
DBCC IND ('tempdb', 'dbo.indexdemo', 4) GO DBCC TRACEON (3604); DBCC PAGE (tempdb, 1, 218, 3); GO

Chez nous, lindex nonclustered nix$dbo_indexdemo$petittexte a lindex_id 4. En excutant DBCC IND, nous voyons que la page contenant le nud racine porte le numro 218. Nous utilisons DBCC PAGE pour voir le contenu de ce nud (plus de dtail sur lutilisation de ces commandes DBCC dans la section Structure interne de lindex ). Le rsultat est prsent figure 6.13.

158

Chapitre 6. Utilisation des index

Figure 6.13 Rsultat de DBCC PAGE

Nous y voyons trois colonnes de cl : petittexte, ID, et un unifiant (UNIQUIFIER), ici toujours NULL. La colonne ID fait donc bien partie de la cl de lindex, dj au nud racine.

Uniquifier Il est utilis dans le cas dun index clustered non unique. Un index clustered comporte, en interne, ncessairement des cls uniques (sinon, comment un Key Lookup pourrait trouver une seule ligne ?). Si un tel index est cr sans contrainte dunicit (index unique, ou sur une cl primaire ou unique), SQL Server ajoute une valeur unifiante la fin de la cl lorsque des valeurs doublonnes sont prsentes dans la cl.

Cela veut-il dire que SQL Server va beaucoup dplacer les lignes de pages ?
SELECT i.index_id, i.name, i.type_desc, i.is_primary_key, fill_factor, INDEX_COL(OBJECT_NAME(i.object_id), i.index_id, ic.index_column_id) as column_name FROM sys.indexes i JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id WHERE i.object_id = OBJECT_ID('dbo.indexdemo') ORDER BY i.index_id, ic.key_ordinal;

Donc, un seek est un parcours de larbre quilibr de lindex, pour obtenir avec la ligne correspondante avec un maximum defficacit. Plus le nombre de seeks est lev, plus lindex est rellement utilis sa pleine capacit. Alors quun scan ( analyse dans la traduction franaise de SQL Server) est le parcours complet du nud feuille (leaf node) de lindex. Le nud feuille est le dernier niveau, autrement dit la base de lindex, o chaque recherche se termine. Un scan sur un index clustered correspond un scan sur la table, puisque le nud feuille dun index clustered est la table elle-mme.

6.1 Principes de lindexation

159

Le lookup (bookmark lookup), ou recherche de cls dans la traduction franaise, correspond la ncessit, lorsquune recherche sur lindex nonclustered a atteint le nud feuille, de retrouver les lignes correspondantes dans la table. Les colonnes lookup de la vue sys.dm_db_index_usage_stats indiquent que lindex a particip une opration de recherche de cls. Elle na de sens que sur un index clustered. En effet, la recherche de cl ne se produit qu partir dun index nonclustered. Cette recherche peut se faire soit sur un RID (Row ID, ou identifiant de ligne) dans le cas dune table heap (sans index clustered), soir sur la cl de lindex clustered si la table en comporte un. Dans ce dernier cas, la recherche de cl se fait donc par un parcours de lindex clustered. Cest ce parcours qui est indiqu dans les colonnes lookup de la vue dynamique. Le bookmark lookup est une opration lourde, parce quelle provoque des lectures alatoires (random IO), cest--dire des lectures non pas en squence de lignes dans les pages, mais de lignes qui peuvent se situer nimporte o. Comme lorsque vous cherchez dans un dictionnaire : il est beaucoup plus coteux de prendre chaque nom dans lindex de fin douvrage, et de vous rendre la bonne page, que davoir un dictionnaire dont les entres sont dj tries, et de feuilleter les pages sur la lettre qui vous intresse. Loptimiseur essaie donc dviter autant que possible le bookmark lookup, en choisissant un scan ds que le nombre de lignes retourner atteint un certain seuil. Un index a une profondeur (le nombre de niveaux de larbre) et une tendue (le nombre de cls, et physiquement de pages chaque niveau). Bien entendu, ltendue augmente au fur et mesure que lon descend dans les niveaux.

6.1.2 Choix de lindex


Dans quel cas choisir un index clustered, et dans quel cas choisir un index nonclustered ? Comme le dernier niveau de lindex est la ligne elle-mme, toute recherche travers lindex clustered sera trs rapide, puisque ltape de lookup sera inutile. Lindex clustered est choisir soigneusement, car, bien entendu, on ne peut crer quun seul index clustered par table (comme il nexiste quun seul ordre alphabtique dans un dictionnaire ce nest que dans le monde de la physique quantique quun objet peut se dupliquer et prendre deux natures diffrentes. Une table ne peut, elle, tre physiquement trie par deux cls diffrentes la fois). Comme lindex clustered ordonne physiquement les lignes, il est aussi idal sur des recherches de plages de valeurs. Les clauses de recherche utilisant un BETWEEN, ou une syntaxe comme :
SELECT * FROM dbo.indexdemo WHERE id > 10 AND id < 50;

profitera aussi grandement dun index clustered.

160

Chapitre 6. Utilisation des index

Dun autre ct, tout ajout dune ligne entrane invitablement une rorganisation physique de la table. Lordre des lignes doit se maintenir, et cela a un cot. Lajout dune ligne dont la cl clustered doit sinsrer lintrieur de lindex, peut provoquer une sparation (split) de page, et donc entraner des critures coteuses du moteur de stockage. Pour viter ce cot de maintenance, il est fortement recommand de crer un index clustered sur une colonne qui augmente de faon squentielle (on dit aussi monotone), comme un auto-incrmental (IDENTITY) ou une colonne dhorodatage. Pour la mme raison, les mises jour de la valeur de la cl sont dconseilles. Pour ces raisons, on cre souvent lindex clustered sur la cl primaire de la table dailleurs la cl primaire est par dfaut cre clustered. Dans un modle de donnes bien conu, quelques rares exceptions prs, la cl primaire est immuable (on ne change pas un identifiant rfrenc dans des tables filles). Lindex clustered est aussi prfrable sur une colonne, ou un ensemble de colonnes, unique. Nous avons vu quen interne, SQL Server doit unifier les cls de lindex clustered si elles ne le sont pas dj, simplement parce que pour ordonner les lignes, il ne peut tolrer de doublon. Crer un index clustered sur une cl non unique augmente la taille de lindex. Cest un dfaut avec lequel on va parfois composer, car les avantages de lindex clustered sont rels. Ceci dit, il ne faut pas sous-estimer limportance de conserver un index clustered aussi petit que possible. Comme sa cl est copie dans la cl de tous les autres index de la table, il influence la taille de tous les index, et donc leur efficacit et leur utilit. Prenons un peu davance sur la section traitant des statistiques, et faisons une exprience :
DROP INDEX cix$dbo_indexdemo$id ON dbo.indexdemo; CREATE CLUSTERED INDEX cix$dbo_indexdemo$texte ON dbo.indexdemo (texte ASC); GO DBCC SHOW_STATISTICS ('dbo.indexdemo', 'nix$dbo_indexdemo$petittexte');

Nous supprimons lindex clustered sur notre table de test, et nous le recrons, mais cette fois-ci sur la colonne texte, qui est de type CHAR(100). Voyons comment lindex nix$dbo_indexdemo$petittexte, sur la colonne petittexte (CHAR(1)), a ragi. La commande DBCC SHOW_STATISTICS nous permet dobtenir des dtails sur la structure de lindex, qui sont ceux quutilise loptimiseur SQL pour juger de sa pertinence pour rpondre une requte. Voyons un extrait du rsultat (nous navons gard que les lignes et colonnes utiles pour notre propos) :
Name Rows Density Average key length ----------------------------------------------------------------------nix$dbo_indexdemo$petittexte 2007 0,1304348 100,0992 All density Average Length Columns ---------------------------------------------0,03703704 0,09915297 petittexte 0,003496503 100,0992 petittexte, texte

6.1 Principes de lindexation

161

Notons au passage ce qui sest produit : nix$dbo indexdemo$petittexte a t recr, puisquil doit contenir la cl de lindex clustered, et que celui-ci a chang. Mfiez-vous de cet effet de bord : la modification dun index clustered entrane la recration de tous les autres index, et donc un travail important, augment dun blocage (verrouillage) de la table durant une priode souvent longue (voyez plus loin loption ONLINE pour limiter ce temps de blocage).

Lindex nix$dbo_indexdemo$petittexte, dont la cl tait auparavant de 5 octets (1 octet pour la colonne petittexte, et 4 octets pour la colonne ID), en fait maintenant (environ) 101. Loptimiseur va orienter son choix en regard de cette information. Plus la cl est longue, plus lindex sera lourd parcourir. La taille maximale dune cl dindex est de 900 octets. Nous en sommes loin ici, mais dj, lutilit de lindex est compromise. Un index dont la cl fait 900 octets, est trs peu utile, et mme dangereux si lindex est clustered.

Couverture de requte
Lindex comporte une ou plusieurs colonnes de la table dans sa cl. Cela veut donc dire que la valeur de ces colonnes est connue de lindex, et que toutes ces valeurs sont prsentes dans son nud feuille. Par exemple, un index sur la colonne LastName de la table Person.Contact contient tous les LastName. Dans ce cas, nous souhaitons dans notre requte ne rcuprer que les noms, lindex tout seul suffit y rpondre :
SELECT DISTINCT LastName FROM Person.Contact WHERE LastName LIKE 'A%' ORDER BY LastName;

Donnera ce plan dexcution1 :


|--Stream Aggregate(GROUP BY:([LastName])) |--Index Seek(OBJECT:([nix$Person_Contact$LastName]), SEEK:([LastName] >= N'A' AND [LastName] < N'B'), WHERE:([LastName] like N'A%') ORDERED FORWARD)

Vous voyez que, non seulement lindex a t utilis, mais en plus en seek (avec une rcriture du LIKE en clause acceptable pour un seek), et non pas en scan, mme si lestimation de la cardinalit retourne est de 911 lignes. Ceci parce quil ny a pas de bookmark lookup, donc une stratgie de seek est beaucoup plus intressante. Cette utilisation de lindex pour retourner les colonnes demandes, est appele couverture. Ici, lindex nix$Person_Contact$LastName couvre notre requte. Un index couvrant est extrmement intressant pour les performances, car lindex est une structure non seulement optimise pour la recherche, mais plus compacte que la table, puisquil contient moins de colonnes : scanner le nud feuille dun index non1. Sur les plans affichs en texte, nous avons simplifi le rsultat pour amliorer la lisibilit. Ces plans seront plus dtaills chez vous.

162

Chapitre 6. Utilisation des index

clustered est beaucoup plus rapide que de scanner la table. De plus, seules les pages dindex seront verrouilles, permettant une plus grande concurrence daccs la table. Que se passe-t-il ds que nous rajoutons une colonne supplmentaire dans le SELECT ?
SELECT LastName, FirstName FROM Person.Contact WHERE LastName LIKE 'A%' ORDER BY LastName, FirstName;

Voici le plan dexcution :


|--Sort(ORDER BY:([LastName] ASC, [FirstName] ASC)) |--Clustered Index Scan(OBJECT:([PK_Contact_ContactID]), WHERE:([LastName] like N'A%'))

Rat ! Il manque lindex nix$Person_Contact$LastName linformation de FirstName. Pour viter le bookmark lookup, loptimiseur prfre parcourir la table. Diffrence de performances ? Un SET STATISTICS IO ON nous donne en lectures, 5 pages pour la premire requte, 1135 pour la seconde Voil qui est dommage, simplement pour une colonne de plus retourner. Pour viter cela, nous allons rendre lindex couvrant. En SQL Server 2000, il ny avait dun moyen de le faire : ajouter les colonnes couvrir en fin de cl de lindex. Ctait efficace, mais avec un lger dsavantage : les colonnes ajoutes alourdissaient toute la structure de lindex, puisquelles faisaient partie de la cl. Depuis SQL Server 2005, nous pouvons utiliser une commande dinclusion, qui ajoute nos colonnes dans le nud feuille de lindex seulement. Ainsi, tous les nuds intermdiaires conservent leur compacit. Les colonnes incluses nont pas besoin dy tre, puisquelles ne servent pas rsoudre la clause de filtre, mais laffichage dans la clause SELECT. Bien entendu, si vous vouliez permettre la recherche par LastName et FirstName, vous ajouteriez la colonne FirstName dans la cl de lindex, au lieu de la mettre en inclusion. La syntaxe de linclusion est la suivante :
CREATE INDEX nix$Person_Contact$LastName ON Person.Contact (LastName) INCLUDE (FirstName) WITH DROP_EXISTING;

Nous avons ajout loption DROP_EXISTING pour recrer lindex sans avoir le supprimer pralablement. Quen est-il de notre requte ? Voici son nouveau plan dexcution :
|--Sort(ORDER BY:([LastName] ASC, [FirstName] ASC)) |--Index Seek(OBJECT:([nix$Person_Contact$LastName]), SEEK:([LastName] >= N'A' AND [LastName] < N'B'), WHERE:([LastName] like N'A%') ORDERED FORWARD)

Le seek est revenu. Compte tenu des gains de performance importants de la couverture de requte, nhsitez pas pratiquer linclusion, mme si vous avez plusieurs colonnes. Nexagrez pas non plus, noubliez pas que lindex un cot de mainte-

6.1 Principes de lindexation

163

nance. La mise jour de colonnes incluses provoque des critures dans lindex, et potentiellement des splits de pages dindex lorsque la colonne est de taille variable.

Index filtr et contrainte dunicit


Nous le savons, NULL nest pas une valeur, cest mme le contraire dune valeur : cest labsence de valeur, linconnu. Comment grer cet inconnu dans une contrainte dunicit ? La norme SQL indique en toute logique quune contrainte dunicit doit accepter de multiples marqueurs NULL. Dans la prise en charge par SQL de la logique trois tats, le NULL est discriminant. Une requte comme :
SELECT * FROM Person.Contact WHERE Title = 'Mr.'

Les lignes dont le Title est NULL ne sont pas retournes. Ces lignes pourraient correspondre un Title = 'Mr.', mais nous nen savons rien, nous ne pouvons donc les considrer. Le mme phnomne sapplique lunicit. Une colonne contenant le marqueur NULL pourrait correspondre une valeur dj entre, mais nous nen savons rien. Par consquent, une contrainte dunicit devrait accepter les NULL, et remettre sa vrification au moment o la colonne est rellement alimente dune valeur. SQL Server ne ragit pas ainsi. Un seul marqueur NULL est accept dans une contrainte dunicit ou dans un index unique, sans doute en partie parce que, pour SQL Server, NULL est une cl dindex comme une autre (une recherche WHERE IS NULL utilise lindex comme toute autre comparaison dgalit). Pour grer ce problme nous avons, en SQL Server 2005, deux solutions : implmenter la contrainte laide dun dclencheur, et donc nous passer de lindex unique, ou crer une colonne calcule persiste et indexe, base sur la colonne unifier, dans laquelle nous remplaons les marqueurs NULL par une valeur unique, par exemple calcule sur la cl primaire, que nous garantissons diffrente de toute valeur possible de la colonne source. Cette deuxime solution peut tre intressante dans certains cas, mais elle est un peu difficile grer, et elle augmente la taille des donnes.

En SQL Server 2008, une clause de filtre est introduite la cration dindex. Elle est intressante pour notre cas, mais aussi pour optimiser des index sur des colonnes dont seules quelques valeurs sont slectives. Vous pouvez simplement ajouter une clause WHERE la commande de cration dindex nonclustered : CREATE UNIQUE INDEX uqf$Person_Contact$EmailAddress ON Person.Contact (EmailAddress) WHERE EmailAddress IS NOT NULL; Les lignes qui ne correspondent pas la clause WHERE ne sont simplement pas prises en compte dans lindex. De fait, ce filtre nous permet dimplmenter un index unique rpondant la norme SQL. La clause WHERE accepte les comparaisons simples, et lutilisation du IN.

Mais la cration dindex filtrs nest pas limite aux index uniques, vous pouvez lutiliser sur des colonnes trs peu slectives pour certaines valeurs et trs slectives

164

Chapitre 6. Utilisation des index

pour dautres (en incluant dans le filtre seulement les valeurs trs slectives), pour diminuer la taille de lindex et le rendre plus efficace. En voici un exemple, qui cre un index filtr sur une colonne de type bit, cense reprsenter un flag actif/inactif :
CREATE TABLE dbo.testfiltered ( id int NOT NULL PRIMARY KEY, Active bit NOT NULL, fluff char(896) NOT NULL DEFAULT ('b')); INSERT INTO dbo.testfiltered (id, Active) SELECT ContactId, 0 FROM AdventureWorks.Person.Contact UPDATE t1 SET Active = 1 FROM dbo.testfiltered t1 JOIN (SELECT TOP (10) id FROM dbo.testfiltered ORDER BY NEWID()) t2 ON t1.id = t2.id CREATE INDEX nix$dbo_testfiltered$active ON dbo.testfiltered (Active) WHERE Active = 1; CREATE INDEX nix$dbo_testfiltered$activeNotFiltered ON dbo.testfiltered (Active); DBCC SHOW_STATISTICS ('dbo.testfiltered', 'nix$dbo_testfiltered$active') DBCC SHOW_STATISTICS ('dbo.testfiltered', 'nix$dbo_testfiltered$activeNotFiltered') SELECT * FROM dbo.testfiltered WHERE Active = 1;

Dans cet exemple, lindex nix$dbo_testfiltered$active est choisi par loptimiseur : il est plus compact que lindex non filtr, et donc plus rapide parcourir. Nous avons dlibrment ajout une colonne nomme fluff pour alourdir la table, donc le nombre de pages qui la contiennent. Nous avons aussi utilis une astuce pour mettre jour dix lignes alatoires, laide dun ORDER BY NEWID() : NEWID() retournant un GUID diffrent chaque ligne. Cette astuce est coteuse en performances, elle nest utiliser que dans ce type de tests.

6.1.3 Cration dindex


Aprs avoir prsent les diffrents types dindex, il nous reste parler simplement de la cration de ceux-ci. Nous avons vu dans nos codes dexemple, la syntaxe de cration. Nous pouvons la rsumer ainsi (les valeurs soulignes reprsentent les valeurs par dfaut et les instructions en italiques ne sont valables que pour SQL Server 2008) :
CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name ON <objet> ( colonne [ ASC | DESC ] [ ,...n ] ) [ INCLUDE ( colonne [ ,...n ] ) ]

6.1 Principes de lindexation

165

[ WHERE <filtre> ] [ WITH ( <option> [ ,...n ] ) ] [ ON { partition_schema ( colonne ) | filegroup | default } ] [ FILESTREAM_ON { filestream_filegroup | partition_schema | "NULL" } ] <option> ::= { PAD_INDEX = { ON | OFF } | FILLFACTOR = fillfactor | SORT_IN_TEMPDB = { ON | OFF } | DROP_EXISTING = { ON | OFF } | ONLINE = { ON | OFF } | ALLOW_ROW_LOCKS = { ON | OFF } | ALLOW_PAGE_LOCKS = { ON | OFF } | MAXDOP = max_degree_of_parallelism | DATA_COMPRESSION = { NONE | ROW | PAGE} [ ON PARTITIONS ( { <partition_number_expression> | <range> } [ , ...n ] ) ] }

Voyons rapidement quelques-unes de ces options. UNIQUE permet de crer une contrainte sur la ou les colonnes qui composent la cl. Au niveau du moteur, elle est strictement quivalente la contrainte dunicit, qui cre elle-mme un index unique de ce type. Pour chaque colonne de la cl, vous pouvez indiquer lordre de tri : ASC ou DESC. Si vous triez principalement en ordre descendant (par exemple les lignes les plus rcentes dabord, laide dun index sur une date), crez lindex directement en DESC, cela rendra le scan plus rapide. Les options FILLFACTOR et PAD_INDEX dterminent lespace laiss libre dans les pages dindex. Lorsque vous crez un index sur une table trs souvent modifie, chaque modification entrane une opration de maintenance de lindex. Nous nous souvenons que lindex maintient chacun de ses niveaux dans lordre de la cl, lintrieur des pages dindex comme de page en page, laide dune liste doublement lie. Cette maintenance entrane des rorganisations dans la page, et entre les pages, lorsque des valeurs de cl sont insres. Imaginons une armoire de botes fiches, contenant par exemple des dossiers individuels. Lorsque de nouveaux dossiers sont ajouts, ils peuvent remplir compltement une bote fiche. Il faut alors prendre une bote vide, linsrer au bon endroit dans larmoire, et y placer les nouveaux dossiers (ce quon appelle un split de page). Lorsque des dossiers sont retirs pour suppression, ils librent de la place dans une bote. Ceci rend lensemble moins compact : un certain nombre de botes ne sont quen partie pleines, voire presque vides et le tout prend plus de place dans larmoire. Chercher un dossier parmi lensemble demande plus de travail, car il faudrait ouvrir plus de botes. Cest le problme que rencontre SQL Server lors dun scan. On appelle ce phnomne la fragmentation interne

166

Chapitre 6. Utilisation des index

(dans les pages). Pour combattre cette situation, il ny a quun moyen, cest la rorganisation complte, de temps en temps. Rorganiser chaque petit changement est impraticable : les employs du service passeraient leur temps tout changer. Un autre problme apparat quand larmoire elle-mme na pas assez de place pour contenir de nouvelles botes sur toutes les tagres. ce moment, lajout dune nouvelle bote ne peut se faire quau fond de larmoire. Il faut alors inscrire sur un post-it fix sur la bote fiche pleine le numro de la bote fiche qui contient les dossiers suivants, pour quon puisse aller la chercher au fond de larmoire. Ds lors, parcourir les dossiers veut dire passer de bote en bote non plus dans lordre des tagres, mais avec de nombreux renvois lemplacement du fond, et avec chaque fois un retour lendroit quitt sur ltagre. Cest ce quon appelle de la fragmentation externe : la liste doublement lie dun niveau dindex pointe sur des pages qui ne sont pas contigus, un scan doit donc faire de nombreux allers et retours entre les extensions. Un split de page pleine entrane en gnral la fois de la fragmentation interne, puisque de nouvelles pages sont cres qui contiennent de lespace vide, et de la fragmentation externe, puisque ces nouvelles pages sont souvent cres dans dautres extensions. Une suppression de ligne ne cre que de la fragmentation interne. Notons galement que les splits ne se produisent pas quaux insertions : une mise jour de ligne, qui augmente la taille dune colonne de type variable, peut provoquer aussi le dplacement des lignes, soit dans un index nonclustered dont il est une partie de la cl, soit dans un index clustered, cest--dire la table elle-mme1. La fragmentation interne est ennuyeuse parce quelle fait grossir la table inutilement. La fragmentation externe lest encore plus, parce quelle diminue les performances en lecture physique en provoquant des lectures alatoires, et quelle diminue aussi les performances dcriture. Les clauses FILLFACTOR et PAD_INDEX servent rduire la fragmentation externe. FILLFACTOR permet dindiquer un pourcentage despace vide dans les pages du nud feuille de lindex, sa cration. Cela permet de conserver suffisamment despace libre pour accommoder de nouvelles lignes sans provoquer de splits, bien entendu au dtriment de la compacit des pages. Sa valeur est 0, qui correspond 100 %. Pour vos tables trs fortement modifies, vous pouvez attribuer une valeur manuelle. Par exemple, un FILLFACTOR de 80 laissera 20 % despace libre dans la page. Pour des tables utilises plutt en lecture (comme dans des applications OLAP), laissez-le en valeur par dfaut, pour obtenir les meilleures performances de vos index en les conservant le plus compacts possible. La colonne fill_factor de la vue systme sys.indexes vous donne le FILLFACTOR dfini de vos index :
SELECT SCHEMA_NAME(o.schema_id) + '.' + o.name as table_name, i.name as index_name, i.fill_factor FROM sys.indexes i
1. Les tables heap se rorganisent diffremment : des renvois denregistrements (forwareded records) sont crs dans ces cas).

6.1 Principes de lindexation

167

JOIN sys.objects o ON i.object_id = o.object_id WHERE fill_factor <> 0 ORDER BY table_name;

Il est vident que le FILLFACTOR nest appliqu qu la cration et reconstruction de lindex. Il nest pas automatiquement maintenu. Si vous rorganisez votre armoire en dcidant de ne remplir vos botes fiches qu moiti pour laisser de la place pour les futurs dossiers, vous nallez pas toujours conserver ce remplissage 50 % lajout de chaque dossier : cela quivaudrait ne rien changer au problme, en prenant plus de place pour le tout. Si vous voulez maintenir une valeur de FILLFACTOR, il faut donc appliquer un plan de maintenance de vos index, pour les rorganiser ou les reconstruire rgulirement. Pour cela, vous disposez de deux commandes : ALTER INDEX... REBUILD reconstruit totalement lindex. Vous pouvez, en dition Entreprise, indiquer une reconstruction ONLINE. Les options de lindex spcifies la cration (dont le FILLFACTOR) sont conserves. En cas de reconstruction dun index clustered, les index nonclustered ne sont pas reconstruits. Pour tout reconstruire en une fois, utilisez cette syntaxe :
ALTER INDEX ALL ON Person.Contact REBUILD;

Vous pouvez changer au passage les options, comme le FILLFACTOR :


ALTER INDEX nix$Person_Contact$LastName ON Person.Contact REBUILD WITH (FILLFACTOR = 50);

En mode de rcupration simple (ou en mode journalis en bloc), cette opration est logue de faon minimale, et est donc plus lgre.

ALTER INDEX... REORGANIZE rorganise seulement le nud feuille de lindex. Cest une commande plus rapide, qui est toujours excute ONLINE, mme en dition Standard. Les pages sont compactes selon la valeur du FILLFACTOR indique la cration de lindex. Cest une commande idale pour traiter rapidement un index peu fragment. Mais, bien sr, la question est de savoir si un index est fragment La fonction de gestion dynamique sys.dm_db_index_physical_stats est l pour a. Dmonstration :
CREATE INDEX nix$Person_Contact$FirstName ON Person.Contact (FirstName) SELECT index_id, * FROM sys.indexes WHERE name = 'nix$Person_Contact$FirstName' AND object_id = OBJECT_ID('Person.Contact') SELECT * FROM sys.dm_db_index_physical_stats (DB_ID(N'AdventureWorks'), OBJECT_ID(N'Person.Contact'), 22, NULL , 'DETAILED')

168

Chapitre 6. Utilisation des index

ORDER BY index_level DESC; ALTER INDEX nix$Person_Contact$FirstName ON Person.Contact REBUILD WITH (FILLFACTOR = 20) -- ou : CREATE INDEX nix$Person_Contact$FirstName ON Person.Contact (FirstName) WITH FILLFACTOR = 20, DROP_EXISTING; SELECT * FROM sys.dm_db_index_physical_stats (DB_ID(N'AdventureWorks'), OBJECT_ID(N'Person.Contact'), 22, NULL , 'DETAILED') ORDER BY index_level DESC;

Les colonnes intressantes retournes par cette fonction sont prsentes dans le tableau 6.1.
Tableau 6.1 Rsultat de sys.dm_db_index_physical_stats Colonne index_depth index_level avg_fragmentation_in_percent fragment_count Signification Profondeur de lindex (nombre de niveaux). 1 au minimum Niveau de lindex. En mode 'DETAILED', la fonction retourne une ligne par niveau. Fragmentation externe moyenne Nombre de groupes de pages conscutives au nud feuille (ou chaque niveau en vision dtaille). Un index totalement dfragment devrait avoir seulement un fragment, puisque toutes les pages se suivent. Plus ce nombre est important, plus il y a de fragmentation externe. Indique combien de pages en moyenne comportent les fragments. Plus ce nombre est important, moins il y a de fragmentation externe, car plus il y a de pages conscutives. Nombre total de pages sur le niveau Pourcentage moyen de remplissage de la page. Donne la fragmentation interne. Nombre denregistrements sur le niveau, donc de cls prsentes Taille du plus petit enregistrement

avg_fragment_size_in_pages

page_count avg_page_space_used_in_percent record_count min_record_size_in_bytes

6.1 Principes de lindexation

169

Colonne max_record_size_in_bytes avg_record_size_in_bytes Signification Taille du plus grand enregistrement Taille moyenne des enregistrements

Attention aux performances Lappel de sys.dm_db_index_physical_stats en mode 'DETAILED' oblige SQL Server parcourir extensivement lindex. viter dappeler la fonction avec ce mode sur tous les index dune base (paramtres NULL).

Au plus simple, vous pouvez vous baser sur les colonnes avg_fragmentation_in_percent et fragment_count. La recommandation de Microsoft est de rorganiser lindex lorsque lavg_fragmentation_in_percent est en dessous de 30 %, et de reconstruire en dessus. Loption SORT_IN_TEMPDB force SQL Server stocker temporairement les rsultats intermdiaires de tris oprs pour crer lindex. Vous trouvez plus dinformations sur ces tris dans lentre de BOL tempdb and Index Creation . Ces tris peuvent gnrer des larges volumes sur des tables importantes, des index clustered, des index nonclustered composites ou des index nonclustered comportant beaucoup de colonnes incluses. Quand loption SORT_IN_TEMPDB est OFF (par dfaut), toute lopration de cration se fait dans les fichiers de la base de donnes qui contient lindex, ce qui provoque beaucoup de lectures et dcritures sur le mme disque, et ralentit les oprations dentres/sorties normales dutilisation de la base. Avec SORT_IN_TEMPDB ON, les lectures et critures sont spares : lectures sur la base, critures sur tempdb, et ensuite linverse. Si tempdb est plac sur un disque ddi, cela peut nettement amliorer les performances. De plus, cela offre plus de chances dune contigut des extensions contenant lindex, car lcriture de la structure de lindex dans le fichier de bases de donnes se fait en une fois. Ne ngligez pas cette option sur des tables grande volumtrie, elle offre souvent de bons gains de performances la cration des index, quand tempdb a son disque ddi. DROP_EXISTING permet de supprimer lindex et de le recrer en une seule commande. Les options de lindex doivent tre spcifies nouveau notamment le FILLFACTOR , ils ne sont pas conservs de lindex existant. La cration dun index pose un verrou sur la table, qui empche la lecture et lcriture en cas dindex clustered (verrou de modification de schma), et lcriture en cas dindex nonclustered (verrou partag). Loption ONLINE permet de crer ou recrer un index sans verrouiller la table. SQL Server utilise pour ce faire la fonctionnalit de row versioning. Pendant toute la phase de cration dindex, les modifications de donnes sont possibles, elles seront ensuite automatiquement synchronises sur le nouvel index. Cette option est valide pour un index nonclustered aussi bien que clustered. Lopration ONLINE est plus lourde, spcialement lorsque les donnes de la table

170

Chapitre 6. Utilisation des index

sont modifies, car SQL Server doit maintenir deux structures dindex, et elle utilise activement tempdb, mais elle offre lavantage dun meilleur accs concurrentiel aux tables. Cette option nest disponible quen dition Entreprise. Pour plus de dtail sur son fonctionnement interne, reportez-vous aux BOL, entre How Online Index Operations Work . ALLOW_ROW_LOCKS et ALLOW_PAGE_LOCKS contrlent le verrouillage sur lindex (donc sur la table dans le cas dun index clustered). Par dfaut, les verrous sont poss sur des cls dindex, et escalads au besoin. Les verrous sur les cls permettent une meilleure concurrence daccs, mais peuvent tre plus coteux grer si beaucoup de lignes sont appeles. ALLOW_ROW_LOCKS OFF dsactive la pose de verrous au niveau cl (ligne), et donc force les verrous sur les pages. Coupl avec ALLOW_PAGE_LOCKS OFF, cela force les verrous sur la table. En gnral il ny a rien changer dans ces options. Elles peuvent ventuellement faire gagner du temps sur des requtes larges de type OLAP, mais dans ces requtes, le passage en niveau disolation READ UNCOMMITTED est plus efficace. Pour plus de prcisions sur le contrle de la granularit de verrouillage, reportez-vous la section 7.2.2. Loption MAXDOP spcifie le degr de paralllisme appliquer lopration de cration de lindex. Le paralllisme pour un CREATE INDEX nest pris en charge que par ldition Entreprise. Vous pouvez crer des index composites, cest--dire des index multicolonnes. Ils sont utiles pour rsoudre des clauses de filtres qui utilisent toujours les mmes colonnes (par exemple une recherche systmatique avec date de dbut et date de fin). Dans ce cas, lordre de dclaration des colonnes dans la cl est important. Placez la colonne sur laquelle vous cherchez le plus souvent, en premier. Ainsi lindex peut tre utile galement pour les recherches sur cette colonne uniquement. Vous pouvez vous reprsenter la cl dun index comme la concatnation de toutes les colonnes qui la composent (plus la cl de lindex clustered la fin). SQL Server pourra utiliser cet index pour une recherche sur la premire colonne, sur la premire et la deuxime, etc. Une recherche exclusivement sur la deuxime colonne ne pourra profiter de lindex, car on ne peut le parcourir quen le prenant au dbut de la cl. Cest tout fait comme un annuaire : vous pouvez chercher par nom, ou par nom et prnom, puisquil est organis par ordre alphabtique de noms de famille. Chercher les personnes ayant un mme prnom dans un annuaire, est impossible. Vous ne pouvez que parcourir toutes les pages. La cl dun index est limite 16 colonnes, et 900 octets. Nen arrivez pas jusque-l Cette limite nest pas applicable aux colonnes incluses. Si les colonnes de lindex composite sont cherches toujours ensembles, placez les colonnes les plus slectives (contenant le plus de valeurs uniques) en premier, cela augmentera la slectivit gnrale de lindex, et le rendra plus utile, et plus intressant pour loptimiseur. Faites ici encore la comparaison avec un annuaire : il est organis par nom et prnom, non par prnom et nom, aussi parce quil est plus rapide, pour trouver Pierre Bourdieu, de chercher Bourdieu, et dans ceux-ci, de trouver Pierre, que linverse.

6.1 Principes de lindexation

171

6.1.4 Optimisation de la taille de lindex


Les index sont stocks dans des pages, tout comme les donnes. Larbre B-Tree possde une profondeur et une tendue. Le principe de lindex est de limiter ltendue par la profondeur : un niveau dindex trop large devient inefficace, il faut alors placer un niveau supplmentaire pour en diminuer la surface. Corollairement, plus la cl de lindex est petite, plus le nombre de cl par page est important, moins lindex doit tre profond, et plus son parcours est optimis. Prenons un exemple pratique. Voici le code, nous le dtaillerons ensuite :
CREATE DATABASE testdb GO ALTER DATABASE testdb SET RECOVERY SIMPLE GO USE testdb CREATE TABLE dbo.testIndex ( codeLong char(900) NOT NULL PRIMARY KEY NONCLUSTERED, codeCourt smallint NOT NULL, texte char(7150) NOT NULL DEFAULT ('O') ) GO DECLARE @v int SET @v = 1 WHILE (@v <= 8000) BEGIN INSERT INTO dbo.testIndex (codeLong, codeCourt) SELECT CAST(@v as char(10)), @v SET @v = @v + 1 END GO CREATE UNIQUE INDEX uq$testIndex$codeCourt ON dbo.testIndex (codeCourt) SELECT name, index_id, type_desc FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.testIndex') /* -- rsultat name index_id type_desc -------------------------- ----------- -----------NULL 0 HEAP PK__testIndex__0425A276 2 NONCLUSTERED uq$testIndex$codeCourt 3 NONCLUSTERED */ SELECT index_depth, index_level, page_count, record_count FROM sys.dm_db_index_physical_stats( DB_ID(), OBJECT_ID('dbo.testIndex'), 2, NULL, 'DETAILED')

172

Chapitre 6. Utilisation des index

/* -- rsultat index_depth index_level ----------- ----------6 0 6 1 6 2 6 3 6 4 6 5 */

page_count -------------------1441 321 72 16 3 1

record_count -------------------8000 1441 321 72 16 3

SELECT index_depth, index_level, page_count, record_count FROM sys.dm_db_index_physical_stats( DB_ID(), OBJECT_ID('dbo.testIndex'), 3, NULL, 'DETAILED') /* -- rsultat index_depth index_level ----------- ----------2 0 2 1 */

page_count -------------------14 1

record_count -------------------8000 14

Nous crons dabord une base de donnes pour y jouer, que nous mettons en mode de rcupration simple afin dviter une augmentation inutile de la taille du journal. Nous crons ensuite une table de test, en faisant en sorte que chaque ligne remplisse une page. Nous y ajoutons deux colonnes pour y appliquer des index : lune fait 900 octets (la taille maximale dune cl dindex), lautre un simple smallint (2 octets). Nous indexons ces deux colonnes. Lune est cl primaire, lautre cl candidate. Le fait que ces index soient uniques na pas dimportance dans cet exemple, la structure de lindex nest en rien diffrente. Nous crons une insertion en boucle, de faon y insrer 8000 lignes. Nous utilisons sys.indexes pour retrouver lidentifiant de chaque index. Lindex lourd porte lID 2, le lger, lID 3. Nous en observons ensuite la taille laide de la fonction systme sys.dm_db_index_physical_stats. Ce que nous constatons, cest que lindex lourd est profond, et ncessairement tendu (peu de cls peuvent rsider dans la mme page) : six niveaux pour un total de 1 854 pages. Lindex lger, lui, comporte deux niveaux pour un total de 15 pages. Observons la diffrence sur les requtes :
SET STATISTICS IO ON SELECT * -- Table SELECT * -- Table FROM dbo.testIndex WHERE CodeLong = '49' 'testIndex'. Scan count 0, logical reads 7 FROM dbo.testIndex WHERE CodeCourt = 49 'testIndex'. Scan count 0, logical reads 3

La recherche avec lindex lger doit lire trois pages, contre sept pages pour celle qui sapplique lindex lourd.

6.1 Principes de lindexation

173

Structure interne de lindex


Nous avons vu dans le chapitre traitant des structures de stockage comment SQL Server rpartit les donnes et les index dans des extensions et des pages. Profitons de cette connaissance, et de lexistence de linstruction non documente DBCC PAGE, pour suivre la trace lutilisation dun index. Reprenons pour cela la table exemple prcdente, et voyons comment le moteur de stockage cherche lintrieur de lindex.

Pour cela, nous devons utiliser une autre commande DBCC non documente : DBCC IND, qui donne la structure interne dun index : chaque page de lindex est retourn, avec son ID, son niveau, et la rfrence de la liste doublement lie : page prcdente et page suivante du mme niveau. Sa syntaxe est :

DBCC IND ('base de donnes', 'table', PartitionId);

Donc, imaginons que nous soyons ce moteur de stockage. Loptimiseur SQL a dcid dune stratgie pour rpondre cette requte :
SELECT * FROM dbo.testIndex WHERE CodeLong = '49'

Lindex dont la cl est la colonne CodeLong, et dont lID est 2, va naturellement tre utilis. Nous pouvons le vrifier avec loption de session SET SHOWPLAN_TEXT ON, qui affiche le plan dexcution en texte, au lieu dexcuter linstruction. Le rsultat appliqu notre requte donne ceci :
|--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000])) |--Index Seek( OBJECT:([testdb].[dbo].[testIndex].[PK__testIndex__0425A276]), SEEK:([testdb].[dbo].[testIndex].[codeLong]=[@1]) ORDERED FORWARD) |--RID Lookup(OBJECT:([testdb].[dbo].[testIndex]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD)

Lindex PK__testIndex... est utilis pour la recherche, et ensuite un RID lookup est effectu, pour rechercher la ligne rfrence dans lindex nonclustered par un RID (Row ID). Nous avons vu dans les statistiques IO, que cela ncessitait la lecture de sept pages. On se souvient que lindex sur CodeLong a six niveaux. Tout ici est donc logique : descente sur six niveaux dindex, plus lecture de la page de donnes rfrence par le RID. Essayons de faire ce parcours nous-mmes. Le code suivant alterne les requtes de DBCC PAGE et leur rsultat :
CREATE TABLE #ind ( PageFID bigint, PagePID bigint, IAMFID bigint, IAMPID bigint, ObjectID bigint, IndexID bigint,

174

Chapitre 6. Utilisation des index

PartitionNumber bigint, PartitionID bigint, iam_chain_type varchar(20), PageType int, IndexLevel int, NextPageFID bigint, NextPagePID bigint, PrevPageFID bigint, PrevPagePID bigint ) GO INSERT INTO #ind EXEC ('DBCC IND(''testdb'', ''dbo.testIndex'', 2)') SELECT * FROM #ind WHERE IndexLevel = 5 -- comment rsoudre cette requte avec un index SET STATISTICS IO ON SELECT * FROM dbo.testIndex WHERE code = '49' /* Table 'testIndex'. Scan count 0, logical reads 7 */ DBCC TRACEON (3604); GO DBCC PAGE (testdb, 1, 8238, 3) /* FileId PageId Row Level ChildFileId ------ ----------- ------ ------ ----------1 8238 0 5 1 1 8238 1 5 1 1 8238 2 5 1 */ -- row, key 45, ChildPageId : 1122 DBCC PAGE (testdb, 1, 1122, 3) /* FileId PageId Row Level ChildFileId ------ ----------- ------ ------ ----------1 1122 0 4 1 1 1122 1 4 1 1 1122 2 4 1 [...] */ -- row, key 487, ChildPageId : 9582 DBCC PAGE (testdb, 1, 9582, 3) /* FileId PageId Row Level ChildFileId ------ ----------- ------ ------ ----------1 9582 0 3 1 1 9582 1 3 1 1 9582 2 3 1 [...]

ChildPageId ----------4179 8239 1122

codeLong (key) ---------NULL 253 45

ChildPageId ----------4180 9582 9121

codeLong (key) ---------45 487 55

ChildPageId ----------9122 9207 9368

codeLong (key) ---------487 495 504

6.1 Principes de lindexation

175

*/ -- row, key 487, ChildPageId : 9122 DBCC PAGE (testdb, 1, 9122, 3) /* FileId PageId Row Level ChildFileId ChildPageId codeLong (key) ------ ----------- ------ ------ ----------- ----------- ---------1 9122 0 2 1 9039 487 1 9122 1 2 1 9076 489 1 9122 2 2 1 9123 491 1 9122 3 2 1 9160 493 */ -- row, key 489, ChildPageId : 9076 DBCC PAGE (testdb, 1, 9076, 3) /* FileId PageId Row Level ChildFileId ChildPageId codeLong (key) ------ ----------- ------ ------ ----------- ----------- ---------1 9076 0 1 1 9073 489 1 9076 1 1 1 9075 4897 1 9076 2 1 1 3623 49 1 9076 3 1 1 9079 4906 */ -- row, key 49, ChildPageId : 3623 DBCC PAGE (testdb, 1, 3623, 3) /* FileId PageId Row Level codeLong (key) HEAP RID ------ ----------- ------ ------ --------------------------------1 3623 0 0 49 0x380C000001000000 1 3623 1 0 490 0x590E000001000000 1 3623 2 0 4900 0x8C23000001000000 [...] */

Nous crons dabord une table temporaire pour stocker le rsultat de DBCC IND. Nous cherchons ensuite dans cette table lID de la page dentre de lindex : son nud racine. Il sagit de la page qui est au niveau le plus lev. Nous regardons ensuite son contenu avec DBCC PAGE. Cela nous retourne une structure qui comporte notamment les colonnes ChildPageId et codeLong (Key), indiquant respectivement lID de la page fille (celle qui se trouve au nud intermdiaire suivant, cest--dire au niveau infrieur de lindex), et la cl qui reprsente la ligne. Chaque entre dindex reprsente une plage de valeurs de cls situe entre la cl indique dans la ligne et la cl de la ligne suivante de ce nud. Par exemple ici, la ligne 2 du nud 5 de lindex, pointe sur des valeurs de cl comprises entre '253' incluse et '45' exclue (noublions pas que cette colonne est un char, pas un numrique, et que la cl est donc trie alphabtiquement). La ligne 3 pointe sur toutes les cls gales ou suprieures '45'. Cest l que nous devons descendre. Nous suivons donc cette voie sur six niveaux, et nous voyons que chaque page rfrence dans ChildPageId est une page situe au niveau infrieur. Lorsque nous arrivons au nud feuille, le niveau 0, le ChildPageId est remplac par un HEAP RID, qui est un stockage binaire du numro de fichier, de page, et de ligne dans la page, de la ligne qui correspond cette valeur. Dans le cas dun index sur une table clustered, nous aurions trouv la cl de lindex clustered.

176

Chapitre 6. Utilisation des index

6.2 VUES DE GESTION DYNAMIQUE POUR LE MAINTIEN DES INDEX


Nous lavons vu, un index cr nest pas forcment utilis. Notamment, loptimiseur peut choisir de parcourir la table au lieu dutiliser lindex, si celui-ci est peu slectif. Tout index possde un cot de maintenance, cest--dire quil doit rpercuter en temps rel toutes les modifications apportes la table sur les colonnes qui composent sa cl. Un index inutilis est donc pnalisant pour les performances, ainsi bien sr que pour lespace de stockage : il consomme de lespace disque sans aucune utilit. Il faut donc le supprimer. Mais comment savoir si un index est utilis ou non ? Vous avez disposition une vue dynamique qui vous offre toutes les informations ncessaires. Cette vue sappelle sys.dm db index usage_stats. Elle affiche les informations suivantes du tableau 6.1.

Tableau 6.2 Rsultat de sys.dm_db_index_usage_stats Colonne user_seeks user_scans user_lookups user_updates last_user_seek last_user_scan last_user_lookup last_user_update system_seeks system_scans system_lookups system_updates last_system_seek Signification nombre de recherches travers lindex dues une requte utilisateur nombre de parcours du nud feuille de lindex dus une requte utilisateur nombre de recherches de cls (bookmark lookups) dues une requte utilisateur nombre de mises jour de lindex dues une requte DML (INSERT, UPDATE, DELETE) utilisateur dernire recherche travers lindex due une requte utilisateur dernier parcours du nud feuille de lindex d une requte utilisateur dernire recherche de cls (bookmark lookups) due une requte utilisateur dernire mise jour de lindex due une requte DML (INSERT, UPDATE, DELETE) utilisateur nombre de recherches travers lindex dues une requte systme nombre de parcours du nud feuille de lindex dus une requte interne nombre de recherches de cls (bookmark lookups) dues une requte interne nombre de mises jour de lindex dues une requte DML (INSERT, UPDATE, DELETE) interne dernire recherche travers lindex due une requte systme

6.2 Vues de gestion dynamique pour le maintien des index

177

Colonne last_system_scan last_system_lookup last_system_update Signification dernier parcours du nud feuille de lindex d une requte systme dernire recherche de cls (bookmark lookups) due une requte systme dernire mise jour de lindex due une requte DML (INSERT, UPDATE, DELETE) systme

Si un scan sexcute sur une table, cest soit quune recherche dans une clause WHERE ne peut tre effectue travers un index (que lindex soit trop peu slectif, soit quil ny a pas de bon index pour la clause de filtre). On peut donc utiliser le nombre de scan pour dtecter un manque dindex ou un index trop peu slectif.

6.2.1 Obtention des informations oprationnelles de lindex


La fonction dynamique sys.dm_db_index_operational_stats permet dobtenir des informations sur la vie et les oprations de maintenance dun index. Il sagit dune fonction, quil faut donc appeler en passant des paramtres : nom de la base, nom de la table, nom de lindex, nom de la partition. Des paramtres passs NULL permettent de retourner des lignes pour chaque index de la base/table. Vous pouvez ainsi mesurer si lindex est plus ou moins coteux maintenir, sil doit oprer de nombreux splits de pages pour accommoder de nouvelles lignes et quels sont les temps dattentes sur lindex.
Tableau 6.3 Rsultat de sys.dm_db_index_operational_stats Colonne partition_number leaf_insert_count leaf_delete_count leaf_update_count leaf_ghost_count Signification numro de partition si la table est partitionne. Base 1 Nombre total dinsertions au niveau feuille de lindex Nombre total de suppressions au niveau feuille de lindex Nombre total de mises jour au niveau feuille de lindex Nombre total de lignes au niveau feuille marques pour suppression, mais pas encore supprimes. Elles seront supprimes par un thread de nettoyage qui sexcute intervalles rguliers. Nombre total dinsertions aux niveaux intermdiaires de lindex. Valeur 0 sur une ligne correspondant une table heap

nonleaf_insert_count

178

Chapitre 6. Utilisation des index

Colonne nonleaf_delete_count Signification Nombre total de suppressions aux niveaux intermdiaires de lindex. Valeur 0 sur une ligne correspondant une table heap Nombre total de mises jour aux niveaux intermdiaires de lindex. Valeur 0 sur une ligne correspondant une table heap Nombre total dallocations de page sur le niveau feuille dun index ou un heap. Sur un index, une allocation de page correspond un page split Nombre total dallocations de page causes par un split, sur les niveaux intermdiaires de lindex. Valeur 0 sur une ligne correspondant une table heap Nombre total de fusions de page sur le niveau feuille Nombre total de fusions de page sur les niveaux intermdiaires. Valeur 0 sur une ligne correspondant une table heap Nombre total de scans (de table ou dune plage plus petite) Nombre total de rcuprations de lignes distinctes depuis lindex ou la table heap Nombre de lignes rcupres travers un enregistrement de renvoi (forwarding record). Valeurs existant seulement sur une table heap. Donc toujours 0 sur des index Nombre total de pages dobjets larges (large object (LOB)) rcupres, depuis des units dallocation LOB_DATA. Signifie la prsence de colonnes TEXT, IMAGE, ou VARCHAR(MAX) ou VARBINARY(MAX), ou XML Nombre total doctets dobjets larges rcuprs Nombre total de valeurs dobjets larges orphelines cres pour des oprations en lot. Valeur toujours 0 sur les index nonclustered Nombre total de valeurs dobjets larges orphelines insres durant les oprations en lot. Valeur toujours 0 sur les index nonclustered Nombre total de pages de dpassement de page (rowoverflow) retournes depuis une unit dallocation ROW_OVERFLOW_DATA

nonleaf_update_count

leaf_allocation_count

nonleaf_allocation_count

leaf_page_merge_count nonleaf_page_merge_count

range_scan_count singleton_lookup_count forwarded_fetch_count

lob_fetch_in_pages

lob_fetch_in_bytes lob_orphan_create_count

lob_orphan_insert_count

row_overflow_fetch_in_pages

6.2 Vues de gestion dynamique pour le maintien des index

179

Colonne row_overflow_fetch_in_bytes Signification Nombre total doctets de dpassement de page (rowoverflow) retourns depuis une unit dallocation ROW_OVERFLOW_DATA Nombre total de valeurs de colonne pousses dans une autre page, pour accommoder soit des valeurs LOB, soit des valeurs en dpassement de page (rowoverflow), durant une insertion ou une mise jour Nombre total de lignes rintgres dans une page de donnes (unit dallocation IN_ROW_DATA), depuis des pages de LOB et de dpassement (unit dallocation LOB_DATA ou ROW_OVERFLOW_DATA), lorsquune mise jour de donnes a diminu la taille de la ligne Nombre total de verrouillages de granularit ligne demands Nombre total dattentes de libration dun verrou de ligne Temps total dattente de libration de verrous de ligne, en millisecondes Nombre total de verrouillages de granularit page demands Nombre total dattentes de libration dun verrou de page Temps total dattente de libration de verrous de page, en millisecondes Nombre total de tentative descalades de verrous Nombre total descalades de verrous rellement effectus Nombre total dattentes de libration dun latch (latch contention) Temps total dattente de libration de latch, en millisecondes Nombre total dattentes de libration dun latch dentre/sortie de page Temps total dattente de libration de latch dentre/ sortie de page, en millisecondes

column_value_push_off_row_count

column_value_pull_in_row_count

row_lock_count row_lock_wait_count row_lock_wait_in_ms page_lock_count page_lock_wait_count page_lock_wait_in_ms index_lock_promotion_attempt_co unt index_lock_promotion_count page_latch_wait_count page_latch_wait_in_ms page_io_latch_wait_count page_io_latch_wait_in_ms

Pour comprendre ce tableau, nous devons expliquer quelques principes supplmentaires :

180

Chapitre 6. Utilisation des index

Lorsquune ligne dans une table heap est mise jour, que la taille de la ligne grandit et quil ne reste plus de place dans la page, la ligne est dplace dans une autre page. Pour viter une mise jour de tous les index nonclustered pour accommoder le nouveau RID de la ligne, SQL Server pose, la place de lancienne ligne, une rfrence vers la nouvelle position de ligne, ce quon appelle un enregistrement de renvoi (forwarding record). Lors dune opration de traitement par lot (bulk operation), SQL Server cre des LOB pour stocker temporairement les donnes. Ces LOB sont dits orphelins (orphan LOB). Vous pouvez donc utiliser cette fonction systme pour collecter des statistiques physiques, dutilisation, de charge ou de contention sur vos tables et vos index. Exemple :
SELECT object_name(s.object_id) as tbl, i.name as idx, range_scan_count + singleton_lookup_count as [pages lues], leaf_insert_count+leaf_update_count+ leaf_delete_count as [critures sur nud feuille], leaf_allocation_count as [page splits sur nud feuille], nonleaf_insert_count + nonleaf_update_count + nonleaf_delete_count as [critures sur nuds intermdiaires], nonleaf_allocation_count as [page splits sur nuds intermdiaires] FROM sys.dm_db_index_operational_stats (DB_ID(),NULL,NULL,NULL) s JOIN sys.indexes i ON i.object_id = s.object_id and i.index_id = s.index_id WHERE objectproperty(s.object_id,'IsUserTable') = 1 ORDER BY [pages lues] DESC

6.2.2 Index manquants


Lors de la gnration du plan dexcution de chaque requte (phase doptimisation), le moteur relationnel teste diffrents plans dexcution et slectionne le moins coteux. Dans certains cas, ce moteur doptimisation a la capacit de constater que la requte aurait t bien mieux servie si un index avait t prsent. Cette information est retourne lorsquon affiche un plan dexcution dtaill en XML. Les informations dindex manquants sont galement stockes dans un espace de cache, et peuvent tre requtes travers trois vues systme, qui offrent toutes les informations ncessaires pour la cration de lindex : colonnes qui devraient composer la cl, colonnes inclure dans le nud feuille, avec un compteur qui indique le nombre de fois o lindex aurait t utile. Cette information nest pas exhaustive, dans le sens ou loptimiseur ne va pas dtecter tous les index manquants. Il ne le fait que sur certaines requtes, lorsquil a la capacit de comprendre quun index aurait t utile. Cela ne vous dcharge pas du travail doptimiser les autres requtes par la cration dindex.

6.3 Vues indexes

181

Mais, concernant les vues dynamiques dindex manquants, voici une requte qui vous donne toutes les informations :
SELECT object_name(object_id) as objet, d.*, s.* FROM sys.dm_db_missing_index_details d INNER JOIN sys.dm_db_missing_index_groups g ON d.index_handle = g.index_handle INNER JOIN sys.dm_db_missing_index_group_stats s ON g.index_group_handle = s.group_handle WHERE database_id = db_id() ORDER BY s.user_seeks DESC, object_id

Comme elle vous dit tout, vous pouvez mme faire gnrer par une requte le code DDL ncessaire la cration des index, ainsi, vous navez plus qu copier ce code et lexcuter. Toutefois, ne crez pas forcment tous les index conseills, mais concentrez-vous sur ceux dont le nombre de user seeks est le plus importants : cette mesure vous indique combien de fois lindex aurait pu tre utile. Voici un exemple de code pour gnrer vos index :
SELECT 'CREATE INDEX nix$' + lower(object_name(object_id)) + '$' + REPLACE(REPLACE(REPLACE(COALESCE(equality_columns, inequality_columns), ']', ''), '[', ''), ', ', '_') + ' ON ' + statement + ' (' + COALESCE(equality_columns, inequality_columns) + ') INCLUDE (' + included_columns + ')', object_name(object_id) as objet, d.*, s.* FROM sys.dm_db_missing_index_details d JOIN sys.dm_db_missing_index_groups g ON d.index_handle = g.index_handle JOIN sys.dm_db_missing_index_group_stats s ON g.index_group_handle = s.group_handle WHERE database_id = db_id() ORDER BY s.user_seeks DESC, objet

Nous verrons dans le chapitre 8 comment lire un plan dexcution, ce qui vous aidera dterminer quels index construire pour vos requtes.

6.3 VUES INDEXES


Une vue est une requte SELECT stocke sur le serveur. On la rfrence comme une table dans une requte. Elle ne matrialise pas le rsultat du SELECT. Donc, chaque appel la vue gnre des appels aux tables sous-jacentes. Vous pouvez indexer vos vues pour matrialiser les donnes. Lorsquune vue est indexe, elle maintient le rsultat de son SELECT dans un index clustered, qui doit lui aussi tre maintenu chaque modification les lignes dans les tables sous-jacentes. Cette option est trs intressante pour les larges volumes, sur des requtes complexes qui mettent en jeu de nombreuses jointures de tables. Elles permettent une sorte de dnormalisation

182

Chapitre 6. Utilisation des index

contrl par le systme. Dans les cas de volumes plus petits, en gnral des index bien placs sur les tables sous-jacentes suffisent.

partir de SQL Server 2005, les vues indexes sont prises en charge par ldition Standard. Une fonctionnalit supplmentaire est prsente dans ldition Entreprise : loptimiseur de requte peut choisir dutiliser un index sur une vue, mme si la requte ne mentionne pas explicitement la vue, mais simplement une table sous-jacente et que loptimiseur trouve un intrt suprieur utiliser cet index.

Votre index doit tre cr avec certaines options de session qui garantissent la consistance des donnes de la vue : ANSI_NULLS ON (aussi la cration de la table) ; QUOTED IDENTIFIER ON, et quelques autres options (voir les BOL) ; La vue doit avoir t cre avec loption WITH SCHEMABINDING (ce qui la lie la structure des tables sous-jacentes, de sorte que les modifications de structure de celles-ci sont protges) ; Les indicateurs de table sont interdits, on ne peut donc forcer un index, ou un niveau disolation, dans la requte ; Les fonctions rfrences dans la vue doivent tre dterministes (elles doivent toujours retourner la mme valeur, donnes gales). Ces options forcent simplement le retour de la vue tre consistant, donc indexable. Les options telles que SET ANSI_NULLS doivent tre places pour toutes les sessions qui requtent la vue, ou modifient les donnes sous-jacentes, afin que SQL Server puisse garantir que le rsultat du SELECT de la vue est toujours le mme. Cette condition peut tre contraignante dans un environnement o vous ne matrisez pas tous les points dentres, et notamment avec des applications qui utilisent des bibliothques daccs aux donnes anciennes, comme ODBC.
Si vous utilisez PHP sous Windows, Microsoft a dvelopp un pilote spcifique, tlchargeable partir de ce lien : http://www.microsoft.com/sql/technologies/php/.

La premire tape est de crer un index clustered unique sur la vue, qui va la matrialiser. Lunicit est ncessaire, vous devez donc trouver un moyen de retourner une colonne ou un jeu de colonnes unique dans votre vue. Voici un exemple de cration de vue, et dutilisation par loptimiseur, mme en mentionnant seulement la table sous-jacente (nous sommes en dition Developer, qui correspond aux fonctionnalits de ldition Entreprise) :
CREATE VIEW Person.SomeContacts WITH SCHEMABINDING AS SELECT ContactID, FirstName, LastName FROM Person.Contact WHERE LastName LIKE '%Ad%'

6.4 Statistiques

183

GO CREATE UNIQUE CLUSTERED INDEX cix$Person_SomeContacts ON Person.SomeContacts (ContactID) CREATE NONCLUSTERED INDEX nix$Person_SomeContacts$LastName_FirstName ON Person.SomeContacts (LastName, FirstName) GO SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE '%Ad%'

Le plan dexcution gnr est simple, un scan... mais de quel index ? Voici le plan :
|--Index Scan(OBJECT:([AdventureWorks].[Person].[SomeContacts]. [nix$Person_SomeContacts$LastName_FirstName]))

6.4 STATISTIQUES
Vous avez fait la connaissance sur un chat Internet dune jeune fille qui vous semble charmante et qui vit Courtrai, en Belgique flamande. Elle semble apprcier vos grandes qualits de cur et vous convenez dun premier rendez-vous, afin de vous connatre en vrai. Comme vous tes galant, vous proposez de faire le voyage Courtrai. Elle vous donne rendez-vous au caf De Brouwzaele. Si vous navez jamais entendu parler de Courtrai, vous ne savez probablement rien de la taille de la ville, de la complexit de ses rues et donc vous ne pouvez pas juger, avant de vous y rendre, si vous trouverez le caf De Brouwzaele facilement. Logiquement, votre premier rflexe sera de vous renseigner sur le nombre dhabitants. Si Courtrai est une ville de quelques milliers dmes, vous pouvez vous dire quelle est peu tendue et que le caf De Brouwzaele sera facile trouver. Par contre, en cherchant sur Wikipdia, vous vous apercevez que Courtrai compte quelque 73 000 habitants. Sachant cela, vous vous attendez plus de difficults. Vous allez donc demander ladresse du caf De Brouwzaele. Kapucijnestraat 19 bien, mais, combien y a-t-il de rues Courtrai, et o se trouve Kapucijnestraat par rapport la gare ? La connaissance de ces dtails risque dinfluencer fortement votre mthode de dplacement. Pour SQL Server, la problmatique est exactement la mme. Imaginons que nous envoyons cette requte :
SELECT * FROM Production.TransactionHistory WHERE ProductId = 800

La premire chose que sait SQL Server, cest quil y a 113 443 lignes dans la table Production.TransactionHistory (dans ce cas, il maintient un compte de lignes dans

184

Chapitre 6. Utilisation des index

la dfinition des index). Ensuite, il sait quil dispose dun index sur la colonne ProductId, qui va lui permettre de filtrer rapidement les enregistrements o ProductId = 800 :
SELECT * FROM sys.indexes i WHERE OBJECT_NAME(i.object_id)

Lindex sappelle IX_TransactionHistory_ProductId. Loptimiseur de SQL Server se base sur lvaluation du cot dun plan dexcution afin de dterminer la meilleure stratgie. Cest ce quon appelle une optimisation base sur le cot (cost-based optimization). Mais pour valuer le cot dun plan dexcution, SQL Server ne peut se contenter des seules informations que nous venons de voir. Il doit disposer dune estimation du nombre de lignes qui va tre retourn, pour juger au plus juste du cot de celle-ci. Cest l o interviennent les statistiques. Si SQL Server dispose de statistiques de distribution des valeurs contenues dans une colonne, il peut valuer avec une bonne prcision le nombre de lignes concernes lorsquune requte filtre sur ces valeurs, car il sait lavance combien de lignes environ correspondent au critre. Les statistiques sont donc un chantillonnage des donnes. Elles sont cres et maintenues automatiquement par SQL Server sur la cl de tout index cr. Elles sont visibles dans les vues sys.stats et sys.stats_columns. Voyons les statistiques de lindex sur ProductId :
SELECT c.name as ColumnName, s.name as IndexName FROM sys.stats s JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id JOIN sys.columns c ON s.object_id = c.object_id AND sc.column_id = c.column_id WHERE s.name = 'IX_TransactionHistory_ProductId'

Tableau 6.4 Nom de lindex ColumnName ProductID IndexName IX_TransactionHistory_ProductID

Cela montre quil existe des statistiques dans cet index pour la colonne ProductId. Elles sont en quelque sorte les informations vitales de lindex, ou son diplme : elles permettent SQL Server, dans sa stratgie, destimer le cot dutilisation de lindex. En dautres termes, grce aux statistiques, SQL Server va pouvoir connatre lavance, approximativement, le rsultat dun :

6.4 Statistiques

185

SELECT COUNT(*) FROM Production.TransactionHistory WHERE ProductId = 800

Si le ratio entre le nombre de lignes qui rpond au critre recherch et le nombre total de ligne est faible, SQL Server va choisir dutiliser lindex. Si par contre il est important, et quune bonne partie des lignes de la table doit tre retourne par la requte, SQL Server va certainement faire le choix de parcourir la table plutt que dutiliser lindex, parce quil sera moins coteux de procder de cette faon que de rsoudre ligne par ligne les adresses de pointeurs contenues dans le dernier niveau de lindex.

6.4.1 Statistiques sur les index


Dans le cas qui nous occupe, ProductId = 800 correspond 416 lignes / 133 443. Voyons le plan dexcution estim en XML :
DBCC FREEPROCCACHE GO SET SHOWPLAN_XML ON GO SELECT * FROM Production.TransactionHistory WHERE ProductId = 800 GO SET SHOWPLAN_XML OFF GO

Un extrait de ce plan :
<RelOp NodeId="0" PhysicalOp="Clustered Index Scan" LogicalOp="Clustered Index Scan" EstimateRows="418" EstimateIO="0.586088" EstimateCPU="0.124944" AvgRowSize="54" EstimatedTotalSubtreeCost="0.711032" Parallel="0" EstimateRebinds="0" EstimateRewinds="0">

Nous voyons que loptimiseur choisit de parcourir la table (donc ici un scan de lindex clustered, puisque le dernier niveau de lindex clustered correspond aux donnes de la table) au lieu dutiliser lindex. Nous voyons aussi dans lattribut EstimateRows que les statistiques permettent loptimiseur davoir une ide assez prcise du nombre de lignes correspondant la clause WHERE. Essayons maintenant dindiquer un nombre plus petit de lignes retourner :
SELECT ProductId, COUNT(*) FROM Production.TransactionHistory GROUP BY ProductId;

Nous voyons par exemple que le ProductId 760 ne se retrouve que six fois dans la table. Essayons avec cela :
DBCC FREEPROCCACHE GO

186

Chapitre 6. Utilisation des index

SET SHOWPLAN_XML ON GO SELECT * FROM Production.TransactionHistory WHERE ProductId = 760 GO SET SHOWPLAN_XML OFF GO

Un extrait du plan dexcution estim :


<RelOp NodeId="2" PhysicalOp="Index Seek" LogicalOp="Index Seek" EstimateRows="5.28571" EstimateIO="0.003125" EstimateCPU="0.000162814" AvgRowSize="15" EstimatedTotalSubtreeCost="0.00328781" Parallel="0" EstimateRebinds="0" EstimateRewinds="0"> ... <Object Database="[AdventureWorks]" Schema="[Production]" Table="[TransactionHistory]" Index="[IX_TransactionHistory_ProductID]" />

Cette fois-ci nous voyons que lindex est utilis : cest la solution la moins coteuse. Lestimation des lignes retourner, base sur les statistiques de distribution, donne 5,28571 lignes. Nous verrons plus loin do SQL Server tire cette approximation.

6.4.2 Colonnes non indexes


Les statistiques ne sont pas uniquement utiles pour les index. Loptimiseur peut profiter de la prsence de statistiques mme sur des colonnes qui ne font pas partie de la cl dun index. Cela lui permettra dvaluer le nombre de lignes retournes dans une requte qui filtre sur les valeurs de cette colonne, et donc de choisir un bon plan dexcution. Par exemple, si la colonne participe un JOIN, cela permettra loptimiseur de choisir le type de jointure le plus adapt. En SQL Server 2000, comme les statistiques de colonne sont stockes dans la table systme sysindexes avec la dfinition des index, la cration de statistiques avait comme consquence de diminuer le nombre possible dindex pouvant tre crs sur la table. Vous tiez limits 249 index nonclustered par table, donc 249 index plus statistiques. Sur des tables contenant beaucoup de colonnes, la cration automatique des statistiques pouvait vous faire atteindre cette limite. Il vous fallait alors supprimer des statistiques (avec la commande DROP STATISTICS) pour permettre la cration de nouveaux index. Dans SQL Server 2005, la limite du nombre de statistiques de colonne sur une table a t augmente 2000, plus 249 statistiques dindex, poussant le nombre de statistiques possibles sur une table 2249.

6.4 Statistiques

187

Pour voir les statistiques cres automatiquement :


SELECT * FROM sys.stats WHERE auto_created = 1

leur nom commence par _WA_Sys_.

6.4.3 Slectivit et densit


Ces statistiques de distribution permettent de dterminer la slectivit dun index. Plus le nombre de donnes uniques prsentes dans la colonne est lev, plus lindex est dit slectif. La plus haute slectivit est donne par un index unique, o toutes les valeurs sont distinctes, et les plus basses slectivits sont amenes par des colonnes qui contiennent beaucoup de doublons, comme les colonnes de type BIT, ou CHAR(1) avec seulement quelques valeurs (par exemple H et F pour une colonne de type sexe). linverse, la densit reprsente le nombre de valeurs dupliques prsentes dans la colonne. Plus il y a de valeurs dupliques, plus la densit est leve. Sur lindex dans son entier, le pourcentage moyen de lignes dupliques donne la slectivit et la densit : plus ce pourcentage est faible, plus lindex est slectif, et plus il est lev, plus lindex est dense. La densit soppose donc la slectivit.

6.4.4 Consultation des statistiques


Nous avons quelquefois entendu des imprcisions au sujet des statistiques. Les statistiques ne sont pas des index, et ne sont pas stockes comme les index ou dans les pages dindex. Notamment les informations de distribution ne se situent pas au niveau des nuds de larborescence de lindex. Si ctait le cas, le plan dexcution ne pourrait pas faire le choix dutiliser lindex ou non... avant de lutiliser, puisquil devrait entrer dans larborescence pour retrouver lestimation de lignes retourner, puis ensuite quitter lindex pour faire un scan si ce choix lui parat meilleur. Ce genre de dcision doit se prendre avant de commencer le processus de recherche des donnes. Ainsi, les donnes de statistiques sont disponibles en tant que mtadonnes de lindex, et non pas dans sa structure propre. Elles sont stockes dans une colonne de type LOB, dans une table systme visible travers la vue sys.sysindexes (du nom dune ancienne table systme de SQL Server 2000). La requte
SELECT statblob FROM sys.sysindexes

permet de voir que la colonne statblob, qui contient ces informations de statistiques, retourne NULL. La colonne contient bien des donnes, mais elles ne sont pas

188

Chapitre 6. Utilisation des index

visibles par ce moyen, et la valeur retourne par requte sera toujours NULL. Dailleurs,
SELECT * FROM sys.indexes;

qui est la vue de catalogue officielle affichant les donnes dindex, ne retourne pas cette colonne. La seule faon de retourner les donnes de statistiques est dutiliser une commande DBCC
DBCC SHOW_STATISTICS

ou dafficher les proprits des statistiques dans lexplorateur dobjets de SQL Server Management Studio (nud Statistiques), page Dtails, qui affiche les rsultats du DBCC SHOW_STATISTICS dans une zone de texte. Exemple avec notre index :
DBCC SHOW_STATISTICS ( 'Production.TralinsactionHistory', X_TransactionHistory_ProductID)

Cette commande retourne un en-tte, la densit par colonne de lindex, et un histogramme de distribution des donnes, selon lchantillonnage opr. Observons dabord le rsultat de len-tte (tableau 6.5).
Tableau 6.5 Rsultat de len-tte Name IX_TransactionHistory_ProductID Updated Apr 26 2006 11:45AM Density 200 0,01581469 Rows 113443 Average key length 8 Rows Sampled 113443

Steps

String Index NO

Vous trouvez dans len-tte les informations gnrales des statistiques : nom, date de dernire mise jour, nombre de lignes dans lindex et nombre de lignes chantillonnes, nombre dtapes (steps) effectues pour effectuer cet chantillonnage, densit de lindex, longueur moyenne de la cl de lindex. Le boolen String Index indique si la colonne contient des rsums de chane, qui est une fonctionnalit de SQL Server 2005 que nous dtaillerons plus loin.
Tableau 6.6 Densit All density 0,0022675736961 8,815E-06 Average Length 4 8 ProductID ProductID, TransactionID Columns

Le deuxime rsultat indique la densit de chaque colonne de lindex. La cl de lindex dpendant toujours de la premire colonne, la densit individuelle est calcu-

6.4 Statistiques

189

le seulement pour celle-ci. Les densits calcules sont ensuite celles des colonnes agrges dans lordre de leur prsence dans lindex (le prfixe de la cl), une colonne aprs lautre, si lindex est composite, ou si lindex est nonclustered et prsent sur une table clustered. Dans ce cas, comme nous le voyons ici, chaque index nonclustered contiendra la ou les colonnes de lindex clustered en dernire position. La densit est donne par le nombre moyen de lignes pour une valeur de la colonne divis par le nombre total de lignes. Le nombre moyen de lignes est calcul en prenant le nombre total de ligne (T) divis par le nombre de valeurs distinctes (VD) dans la colonne. Ce qui donne : (T / VD) / T qui quivaut 1 / VD Et en effet :
SELECT 1.00 / COUNT(DISTINCT ProductId)) FROM Production.TransactionHistory

retourne bien 0,0022675736961 Plus la densit est faible, plus la slectivit est leve, et plus lindex est utile. La slectivit maximale est offerte par un index unique, comme ici lindex clustered unique sur la cl primaire de la table. La densit de la colonne unique est donc 1 / VD, qui correspond logiquement 1 / T, puisque le nombre de valeurs distinctes quivaut au nombre de lignes de la table.
SELECT 1.00 / COUNT(*) FROM Production.TransactionHistory

retourne 0,0000088149996, ce qui reprsente une trs basse densit, donc une slectivit trs leve. Pour vrifier que nous obtenons bien la mme valeur que la deuxime densit (ProductID, TransactionID), forons la notation scientifique en retournant une donne FLOAT :
SELECT CAST(1.00 as float) / COUNT(*) FROM Production.TransactionHistory

qui donne une valeur approximative de 8,815E-06, CQFD. Le couple ProductID + TransactionID ne peut tre que dune slectivit maximale, puisquincluant une colonne unique, chaque ligne est donc unique, et sa densit est 1 / T.
Tableau 6.7 chantillonnage RANGE _HI_KEY 1 3 316 RANGE _ROWS 45 45 144 DISTINCT _RANGE_ROWS 0 1 1 AVG _RANGE_ROWS 1 45

EQ_ROWS 45 307 307

45

190

Chapitre 6. Utilisation des index

RANGE _HI_KEY 319 ... 779 37 771 7 5,285714 RANGE _ROWS 218 EQ_ROWS 118 DISTINCT _RANGE_ROWS 2 AVG _RANGE_ROWS 72

Avec les deux affichages prcdents, loptimiseur SQL sait quel est le nombre de lignes dans la table, et le nombre de valeurs distinctes dans la colonne indexe. Il lui faut maintenant pouvoir valuer, pour une certaine valeur de colonne, quel sera le nombre approximatif de lignes concernes (combien ProductId = 760 concerne-t-il de lignes ?). Pour cela, il maintient un chantillonnage des valeurs tabli selon un certain nombre de sauts (les steps que nous avons vus dans les informations de header), sous forme dun tableau. Dans notre exemple, un tableau de 200 steps. SQL Server cre un maximum de 200 steps par statistiques. La signification des colonnes est dtaille dans le tableau 6.8.
Tableau 6.8 Signification des colonnes Colonne RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS Contenu La valeur de colonne de lchantillon, donc de la dernire ligne de lensemble chantillonn (le bucket) Le nombre de lignes entre lchantillon et lchantillon prcdent, ces deux chantillons non compris Nombre de lignes dans lensemble dont la valeur est gale celle de lchantillon Nombre de valeurs distinctes dans lensemble Nombre moyen de lignes de lensemble ayant la mme valeur, donc RANGE_ROWS / DISTINCT_RANGE_ROWS

Nous voyons donc ici comment loptimiseur a estim le nombre de lignes retournes par la clause WHERE ProductId = 760 : il sest positionn sur lchantillon 779, puisque la valeur 760 est contenue dans lensemble chantillonn de cette ligne, et a retrouv la valeur de AVG_RANGE_ROWS. Dans la case de ProductId = 800, loptimiseur a trouv la ligne o le RANGE_HI_KEY = 800. Le nombre dEQ_ROWS est 418. Pourquoi loptimiseur fait-il le choix de parcourir la table au lieu dutiliser lindex ? Sachant quil aura retourner 418 lignes, cela fait donc potentiellement un maximum de 418 pages de 8 Ko en plus des pages de lindex parcourir. Nous savons, grce la requte suivante, combien de pages sont utilises par chaque index :
SELECT o.name AS table_name, p.index_id, i.name AS index_name,

6.4 Statistiques

191

au.type_desc AS allocation_type, au.data_pages, partition_number FROM sys.allocation_units AS au JOIN sys.partitions AS p ON au.container_id = p.partition_id JOIN sys.objects AS o ON p.object_id = o.object_id LEFT JOIN sys.indexes AS i ON p.index_id = i.index_id AND i.object_id = p.object_id WHERE o.name = N'TransactionHistory' ORDER BY o.name, p.index_id
Tableau 6.9 Allocation des index index_name PK_TransactionHistory_TransactionID IX_TransactionHistory_ProductID IX_TransactionHistory_ReferenceOrderID _ReferenceOrderLineID allocation_type IN_ROW_DATA IN_ROW_DATA IN_ROW_DATA data_pages 788 155 211

Nous pouvons donc assumer quun scan de la table (donc de lindex clustered) cotera la lecture de 788 pages, donc 788 reads. Cela reprsente bien plus que 418 pages. Pourquoi choisir un scan ? Analysons ce qui se passe rellement lorsque nous utilisons un plan ou un autre. Nous pouvons le dcouvrir en forant loptimiseur utiliser lindex en ajoutant un indicateur de table dans la requte (nous verrons les indicateurs de table dans la section 8.2.1) :
SET STATISTICS IO ON GO SELECT * FROM Production.TransactionHistory WITH (INDEX = IX_TransactionHistory_ProductID) WHERE ProductId = 800

Voici les rsultats de pages lues (reues grce SET STATISTICS IO ON), et le plan dexcution utilis :
Table 'TransactionHistory'. Scan count 1, logical reads 1979, physical reads 3, read-ahead reads 744, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Relanons ensuite la requte en laissant SQL Server choisir son plan :


SELECT * FROM Production.TransactionHistory WHERE ProductId = 800

Rsultat :
Table 'TransactionHistory'.

192

Chapitre 6. Utilisation des index

Scan count 1, logical reads 792, physical reads 0, read-ahead reads 44, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server essaie autant que possible dviter le bookmark lookup. Le plan gnr utilise donc une jointure entre les deux index : lindex nonclustered IX_TransactionHistory_ProductID est utilis pour chercher les lignes correspondant au critre, et une jointure de type nested loop est utilise pour parcourir lindex clustered chaque occurrence trouve, pour extraire toutes les donnes de la ligne (SELECT *). Cette jointure doit donc lire bien plus de pages que celles de lindex, puisquil faut aller chercher toutes les colonnes de la ligne. chaque ligne trouve, il faut parcourir lindex clustered pour retrouver la page de donnes et en extraire la ligne. En loccurrence cela entrane la lecture de 1 979 pages. Nous avons vu que lindex clustered utilise 788 pages. Le scan de cet index, selon les informations dentres/sorties, a cot 792 lectures de page. Do sortent les quatre pages supplmentaires ? Selon la documentation de la vue sys.allocation_units dont la valeur 788 est tire, la colonne data_pages exclut les pages internes de lindex et les pages de gestion dallocation (IAM). Les quatre pages supplmentaires sont donc probablement ces pages internes. Nous pouvons le vrifier rapidement grce DBCC IND :
DBCC IND (AdventureWorks, 'Production.TransactionHistory', 1)

o le troisime paramtre est lID de lindex. Lindex clustered prend toujours lID 1. Le rsultat de cette commande nous donne 792 pages, donc 788 sont des pages de donnes, plus une page dIAM (PageType = 10) et trois pages internes (PageType = 2). Voil pour le mystre.

Rsums de chane
Les rsums de chane (string summary) sont une addition aux statistiques dans SQL Server 2005. Comme vous lavez vu dans le header retourn par DBCC SHOW_STATISTICS, la valeur de String Index indique si les statistiques pour cette cl de lindex contiennent des rsums de chane. Il sagit dun chantillonnage lintrieur dune colonne de type chane de caractres (un VARCHAR par exemple), qui permet loptimiseur davoir une meilleure estimation du nombre de lignes que la requte va retourner. Ces statistiques ne sont pas utiles pour un seek dindex, bien sr, puisque la recherche ne pourra se faire sur une cl dindex, mais elles permettent simplement damliorer lestimation de cardinalit. Les rsums de chane ne couvrent que 80 caractres de la donne (les 40 premiers et 40 derniers si la chane est plus longue).

6.4 Statistiques

193

6.4.5 Maintien des statistiques


Consultation
Nous pouvons vrifier la prsence de statistique de plusieurs manires : sp_helpstats : est lancienne mthode dobtention des statistiques. Ne lutilisez plus et prfrez les vues de catalogues suivantes. sys.stats : liste des statistiques prsentes dans la base de donnes. sys.stats_columns : colonnes prsentes pour chaque statistique.
SELECT * FROM sys.stats s JOIN sys.stats_columns sc ON s.object_id = sc.object_id AND s.stats_id = sc.stats_id JOIN sys.columns c ON s.object_id = c.object_id AND sc.column_id = c.column_id

STATS_DATE() : vous pouvez rcuprer la date de dernire mise jour des statistiques en utilisant la nouvelle fonction STATS_DATE() de SQL Server 2005, laquelle vous passez un ID de table et un ID dindex.
SELECT OBJECT_NAME(i.object_id) AS table_name, i.name AS index_name, STATS_DATE(i.object_id, i.index_id) FROM sys.indexes AS i WHERE OBJECT_NAME(i.object_id) = N'TransactionHistory'

DBCC SHOW_STATISTICS : comme nous lavons dj vu, DBCC SHOW_STATISTICS affiche les dtails dun jeu de statistiques.

Cration
Les statistiques sur les index sont cres automatiquement, sans possibilit de dsactivation : un index sans statistiques serait inutilisable. Les statistiques sur les colonnes non indexes peuvent tre cres manuellement ou automatiquement. Par dfaut, les bases de donnes sont cres avec loption AUTO_CREATE_STATISTICS ON, cest--dire que les statistiques dont loptimiseur de requtes a besoin pour gnrer son plan dexcution seront gnres la vole. Certes, il y aura un ralentissement du service de la requte d la cration des statistiques, mais cela ne se produira que la premire fois : potentiellement ce ralentissement sera moindre que celui induit par un plan dexcution mal valu. Cette fonctionnalit, de cration et mise jour automatique des statistiques par SQL Server, est appele Auto Stats. Pour crer manuellement des statistiques sur une ou plusieurs colonnes, vous pouvez utiliser la commande CREATE STATISTICS:
CREATE STATISTICS statistics_name ON { table | view } ( column [ ,...n ] ) [ WITH

194

Chapitre 6. Utilisation des index

[ [ FULLSCAN | SAMPLE number { PERCENT | ROWS } [ NORECOMPUTE ] ] ;

Il peut tre intressant par exemple de crer manuellement des statistiques sur une combinaison de colonnes pour amliorer lestimation du plan dune requte dont la clause WHERE filtre sur ces colonnes. WITH FULLSCAN vous permet dindiquer que toutes les valeurs doivent tre parcourues. Sur les tables volumineuses, lchantillonnage ne se fait pas sur toutes les valeurs, les statistiques seront donc moins prcises. En indiquant WITH FULLSCAN (ou WITH SAMPLE 100 PERCENT, qui est quivalent), vous forcez SQL Server prendre en compte toutes les lignes. Pour les tables de taille moyenne, lchantillonnage se fait de toute manire sur toutes les valeurs. SQL Server sassure un minimum de 8 Mo de donnes chantillonnes (1024 pages ou plus), ou la taille de la table si elle pse moins. linverse, vous pouvez demander un chantillonnage moins prcis avec loption WITH SAMPLE. Notez que si SQL Server considre que votre sampling nest pas suffisamment lev pour gnrer des statistiques utiles, il corrige lui-mme la valeur la hausse. Avec NORECOMPUTE, vous forcez SQL Server ne jamais mettre jour automatiquement ces statistiques. Ne lutilisez que si vous savez ce que vous faites. Vous pouvez supprimer des statistiques avec linstruction DROP STATISTICS. Vous ne devriez normalement jamais avoir faire cela. Vous pouvez, grce la procdure sp_createstats crer en une seule fois des statistiques pour toutes les colonnes de toutes les tables de votre base. Cela est superflu dans la plupart des cas : pourquoi maintenir des statistiques sur des colonnes jamais filtres ?

Mise jour
La mise jour des statistiques est importante. Pour reprendre notre exemple, supposons quavant de partir pour Courtrai, vous en parliez avec votre grand-pre qui vous annonce quil connat bien Courtrai pour y avoir pass ses vacances dt pendant plusieurs annes avec sa famille lorsquil tait enfant. Par politesse, vous vitez de commenter les choix de destination de vacances de vos arrires grandsparents, dautant plus quil se souvient davoir eu en son temps un plan dtaill de la ville. Intressant, cela vous permettrait de vous faire une ide plus prcise de vos dplacements. Aprs quelques recherches au grenier, il vous ressort un vieux plan trs jauni, qui date de 1933. Pensez-vous rellement quil va vous tre trs utile, sachant que la ville a t trs endommage par les bombardements de 1944, et quune grande partie de celle-ci a t reconstruite aprs la guerre ? Le contenu de votre table volue, et vous ne pouvez baser ad vitam aeternam lvaluation de la slectivit dune colonne sur les statistiques telles quelles ont t cres. Cette mise jour doit se faire rgulirement et prendre en compte les larges

6.4 Statistiques

195

modifications de donnes. Comme la cration, elle peut se dclencher automatiquement ou manuellement. Automatiquement avec loption de base de donnes AUTO UPDATE STATISTICS, quil est vivement recommand de laisser sa valeur par dfaut ON.
SELECT DATABASEPROPERTYEX('IsAutoUpdateStatistics') -- pour consulter la valeur actuelle ALTER DATABASE AdventureWorks SET AUTO_UPDATE_STATISTICS [ON|OFF] -- pour modifier la valeur

En SQL Server 2000, la mise jour automatique des statistiques tait dclenche par un nombre de modifications de lignes de la table. Chaque fois quun INSERT, DELETE ou une modification de colonne indexe tait ralis, la valeur de la colonne rowmodctr de la table sysindexes tait incrmente. Pour que la mise jour des statistiques soit dcide, il fallait que la valeur de rowmodctr soit au moins 500.
Vous trouverez une explication plus dtaille de ce mcanisme dans le document de la base de connaissances Microsoft 195565 (http://support.microsoft.com/kb/ 195565).

Avec SQL Server 2005, ce seuil (threshold) est gr plus finement. La colonne rowmodctr est toujours maintenue mais on ne peut plus considrer sa valeur comme exacte. En effet, SQL Server maintient des compteurs de modifications pour chaque colonne, dans un enregistrement nomm colmodctr prsent pour chaque colonne de statistique. Cependant cette colonne nest pas visible. Vous pouvez toujours vous baser sur la valeur de rowmodctr pour dterminer si vous pouvez mettre jour les statistiques sur une colonne, ou si la mise jour automatique va se dclencher bientt, car les dveloppeurs ont fait en sorte que sa valeur soit raisonnablement proche de ce quelle donnait dans les versions suivantes. Vous pouvez activer ou dsactiver la mise jour automatique des statistiques sur un index, une table ou des statistiques, en utilisant la procdure stocke sp_autostats :
sp_autostats [@tblname =] 'table_name' [, [@flagc =] 'stats_flag'] [, [@indname =] 'index_name']

Le paramtre @flagc peut tre indiqu ON pour activer, OFF pour dsactiver, NULL pour afficher ltat actuel. sp_updatestats met jour les statistiques de toutes les tables de la base courante, mais seulement celles qui ont dpass le seuil dobsolescence dtermin par rowmodctr (contrairement SQL Server 2000 qui mettait jour toutes les statistiques). Cela vous permet de lancer une mise jour des statistiques de faon contrle, durant des priodes creuses, afin dviter dventuels problmes de performances dus la mise jour automatique, dont nous allons parler dans la section suivante.

196

Chapitre 6. Utilisation des index

Nous avons vu que les mises jour de statistiques sont indispensables la bonne performance des requtes, et que leur maintenance est ncessaire pour assurer une bonne qualit travers le temps. Mais, quand cette opration se dclenche-t-elle ? Tout comme la cration automatique, elle se produit au besoin, cest--dire au moment o une requte va sexcuter et pour laquelle ces statistiques sont utiles, aussi bien dans le cas dune requte ad hoc (cest--dire dune requte crite une fois pour un besoin particulier), que dune procdure stocke. Si le seuil de modification de la table est atteint, il va dabord lancer une mise jour des statistiques ncessaires, puis ensuite gnrer son plan dexcution. Cela peut videmment provoquer une latence lexcution de la requte. Pour les plans dexcution dj dans le cache, la phase dexcution inclut une vrification des statistiques, un UPDATE ventuel de celles-ci, puis une recompilation, ce qui dans certains cas peut se rvler encore plus lourd. Cela pose rarement un problme, mais si vous tes dans le cas o cela affecte vos performances, vous avez quelques solutions votre disposition : AUTO_UPDATE_STATISTICS_ASYNC (option de base de donnees) vous permet de raliser une mise jour des statistiques de faon asynchrone. Cela veut dire que lorsque le moteur SQL dcide de mettre jour ses statistiques il nattend pas la fin de cette opration pour excuter la requte dclenchante. Elle dmarre donc plus vite, mais ne sexcute pas forcment plus rapidement, car avec un plan dexcution potentiellement obsolte. Les excutions suivantes profiteront des statistiques rafrachies. En temps normal, cette option nest pas ncessaire. Vous pouvez mettre jour manuellement vos statistiques durant vos fentres dadministration, en utilisant par exemple sp_updatestats, ou une tche dun plan de maintenance.

6.5 DATABASE ENGINE TUNING ADVISOR


Le Database Engine Tuning Advisor (DTA), traduit maladroitement en SQL Server 2005 par assistant de paramtrage de base de donnes , est un outil graphique prenant en charge lanalyse dune charge de travail (workload) pour fournir automatiquement des recommandations de cration de structures physiques de donnes (physical design structures, PDS) destines amliorer les performances : index, vues indexes et partitions. La charge de travail est soumise par le DTA au moteur doptimisation de SQL Server, avec diffrentes options, pour juger des PDS les plus aptes amliorer les performances du workload. DTA remplace loutil disponible en SQL Server 2000, nomm Index Tuning Wizard (ITW). Vous apprcierez le glissement smantique : du magicien (wizard), nous passons au conseiller (advisor), un sursaut dhumilit bienvenu (mme si le DTA est plus puissant que lITW) : bien que trs utile et intressant pour faire un

6.5 Database Engine Tuning Advisor

197

premier travail doptimisation, par exemple la mise en place dune nouvelle base en production, loutil nest pas miraculeux, et ne peut remplacer le travail doptimisation manuel, qui, par lalliance des outils de tuning de SQL Server, de vos connaissances et de votre rflexion, reste un travail continu et essentiellement artisanal, ce qui en fait lintrt. Pour sassurer dobtenir des conseils de bonne qualit de la part du DTA, llment essentiel est de disposer dun bon workload. Le DTA analyse un jeu de requtes SQL sappliquant une ou plusieurs bases de donnes, dans lesquelles vous pouvez ventuellement choisir des tables spcifiques. Ce workload doit tre aussi reprsentatif que possible : toutes les requtes importantes doivent y figurer. Si ce nest pas le cas, le DTA risque de fournir des recommandations incompltes, voire nuisibles aux performances des requtes absentes. Il peut tre de diffrents formats. DTA se nourrit soit de simples fichiers de batches de requtes T-SQL, o vous pouvez sparer les batches par linstruction GO, soit de traces SQL sauvegardes dans un fichier ou dans une table. Vous pouvez galement insrer les commandes SQL analyser dans un fichier XML au format spcifique au DTA1, ce qui vous permet dajouter un poids relatif chaque requte pour favoriser les recommandations sur des requtes plus importantes que dautres. La trace SQL doit contenir les vnements suivants : RPC:Completed SQL:BatchCompleted SP:StmtCompleted et au moins les colonnes EventClass et TextData. La colonne Duration est aussi utilise par le DTA, qui se concentre lorsquelle est prsente sur les instructions les plus longues excuter. Le modle (template) du profiler nomm tuning est dj prt pour vos sessions de trace adaptes. videmment, il est important de tracer lactivit de la base un moment de forte utilisation, lorsquelle travaille vraiment, sur un serveur de production, afin dobtenir le workload le plus proche possible du rel. Vous pouvez configurer votre trace pour la rotation de fichiers (rollover), le DTA reconnat les fichiers numrots. Si vous sauvegardez votre trace dans une table, celle-ci doit tre locale au serveur sur lequel vous allez lancer le DTA, et la trace doit tre arrte. Si vous utilisez un fichier de requtes, inscrivez-y simplement une seule fois chaque requte, en essayant de donner aux critres de filtre dans la clause WHERE de valeurs moyennes et reprsentatives : nous avons vu que, grce aux statistiques, loptimiseur est sensible la slectivit de ces valeurs. Les requtes dinsertion, de mise jour et de suppression sont aussi importantes. Non content dtre elles aussi optimisables, elles donnent loccasion au DTA de tester le cot de maintenance des index quil va suggrer.
1. Ce document XML est dcrit par le schma DTASchema.xsd disponible ladresse http://schemas.microsoft.com/sqlserver/. Ce schma dcrit toutes les parties que nous aborderons dans ce chapitre, notamment la dfinition de la session.

198

Chapitre 6. Utilisation des index

Vous pouvez lancer le DTA depuis SSMS (menu Outils/Assistant paramtrage de base de donnes ou Tools/Database engine tuning advisor), ou depuis le menu Microsoft SQL Server 200X / Performance Tools / Database Engine Tuning Advisor. Lexcutable sappelle dtashell.exe. Le DTA peut tre aussi pilot en ligne de commande, via dta.exe, nous laborderons plus loin. Dans SSMS, vous pouvez galement slectionner un batch de requtes, et lancer le DTA avec un clic droit, et la commande Analyze Query in Database Engine Tuning Advisor . Pour utiliser le DTA, vous devez faire partie du rle serveur sysadmin, ou du rle de base de donnes db_owner, des bases analyser. Aprs connexion au serveur, le DTA souvre sur la fentre reproduite en figure 6.14.

Figure 6.14 Premier cran du DTA

Vous voyez sur la gauche un moniteur de sessions : vous pouvez conserver vos sessions, avec les paramtres et recommandations produites, et les rouvrir ou les relancer au besoin. Un clic droit sur une session vous permet, entre autres, de la cloner pour baser une nouvelle analyse sur son jeu de paramtres. Vous pouvez la supprimer de la mme manire. Les sessions (paramtres, recommandations, rapports, etc.) sont enregistres sur le serveur SQL, dans la base de donnes systme msdb. Un jeu de 17 tables est cr dans cette base au premier enregistrement dune session. Elles portent toutes le pr-

6.5 Database Engine Tuning Advisor

199

fixe DTA_. Une procdure stocke, msdb.dbo.sp_DTA_help_session, vous permet de voir rapidement les sessions enregistres. Cest cette procdure qui est appele par le DTA pour remplir les informations de la fentre du moniteur. La partie Workload vous permet de choisir un fichier ou une table. La table doit avoir t gnre par le profiler. Vous choisissez ensuite la base de donnes dans laquelle les requtes du workload sexcutent, il sagit du contexte de dpart, comme lorsque vous ouvrez une fentre dans SSMS. Les ventuelles instructions USE dans le workload seront honores et modifieront le contexte, ce qui permet, dans les faits, de travailler sur plusieurs bases de donnes dans la mme session. Le DTA est encore plus subtil lorsquil analyse une trace : si la colonne LoginName est prsente, il valuera linstruction avec les privilges et dans le contexte de la connexion (login) originale (par exemple pour rsoudre le nom des objets non prfixs par un schma, selon le schma par dfaut de lutilisateur) ; de plus, il se sert de la colonne dvnement DatabaseName pour savoir dans quel contexte valuer chaque instruction. Si cette colonne est absente, il utilise DatabaseID, sinon la base de donnes indique dans la fentre du DTA est utilise. En bref, le DTA essaie autant que possible de reproduire la session utilisateur dans laquelle sest excute linstruction.

Attention Si vous voulez que le DTA se base sur la colonne DatabaseID, assurezvous que la session sexcute sur le mme serveur que celui sur lequel la trace a t enregistre, et que les bases nont pas t dtaches/attaches, sinon, le DatabaseID peut ne plus correspondre aux bases originales. Il est plus sr de se baser sur la colonne DatabaseName.

Ensuite, vous pouvez choisir quelles sont les bases de donnes, et ventuellement les tables, qui seront concernes par lanalyse. Si vous avez dans votre trace des requtes sur des objets dont vous ne voulez pas modifier la structure physique, et sur lesquelles vous ne souhaitez aucun conseil, ne les slectionnez pas ce niveau. Pour slectionner toutes les bases et toutes leurs tables, cliquez dans la case situe sur lentte de la grille. La coche Save tuning log permet de conserver le journal, qui enregistre des messages importants sur lanalyse, notamment quand une table ne peut tre analyse, soit parce quelle nexiste pas, soit parce que sa cardinalit est faible (moins de dix lignes), et quil est inutile dajouter quelque index que ce soit.

200

Chapitre 6. Utilisation des index

Figure 6.15 Deuxime cran du DTA

Le deuxime onglet, reproduit dans la figure 6.15, permet dindiquer les options de recommandations. La premire option permet de limiter la dure de lanalyse. Cela peut tre utile sur un serveur de production pour lancer une analyse durant une fentre administrative, et viter de dborder sur la reprise du travail. Le dsavantage est que la limitation de la dure de lanalyse diminue la qualit des recommandations : le DTA travaille de faon incrmentale, en analysant une partie des instructions la fois, en affinant au fur et mesure ses recommandations. Un bon compromis pour raliser une analyse correcte sans impact sur la production est de dplacer lanalyse sur un serveur de test, nous le verrons un peu plus loin. Si la fin du temps indiqu, le DTA na pas pu terminer son travail, vous en trouverez la mention dans le journal, avec un message du genre Database Engine Tuning Advisor was unable to tune the entire workload . La partie nomme Physical Design Structures (PDS) to use in database permet dindiquer quelles structures seront suggres par le DTA. Les options sont mutuellement exclusives (boutons radio). Vous pouvez demander des suggestions dindex et vues indexes, seulement vues indexes, seulement index, et seulement index nonclustered. Loption Evaluate utilization of existing PDS only est utile en conjonction avec loption Do not keep any existing PDS , pour tester lutilit des index et vues

6.5 Database Engine Tuning Advisor

201

indexes actuelles, et supprimer ventuellement celles qui sont inutiles. Vous pouvez obtenir des informations similaires avec la vue de gestion dynamique sys.dm_db_index_operational_stats, prsente dans la section 6.2.1. La partie partitioning strategy to employ permet, sur une dition Entreprise, de suggrer le partitionnement de tables. Avec loption Full partitioning , des suggestions de partitionnement de tables peuvent tre faites. Aligned partitioning indique au DTA daligner les index suggrs sur le schma de partitionnement de la table. La partie Physical Design Structures to Keep in the Database permet dindiquer ce que le DTA doit conserver, des PDS existants. Do not keep existing PDS est utile pour optimiser un workload complet, par exemple la mise en production dune nouvelle base. Par opposition, choisissez Keep all existing PDS lorsque votre workload nest pas complet ou reprsentatif, o lorsque vous utilisez le DTA pour analyser une slection dans SSMS. Cela vous vitera de supprimer des PDS utiles par ailleurs. Dans ce cas, il est aussi logique de ne spcifier que des recommandations dindex nonclustered, ou de vues indexes.

Attention Si le DTA vous recommande de supprimer beaucoup de vos index existants, alors que vous savez quils sont utiles, cest certainement parce que votre workload nest pas reprsentatif, et quun nombre de requtes importantes ny figure pas. Le DTA considre toujours le workload comme reprsentatif.

Figure 6.16 Options avances du DTA

Les options avances, montres dans la figure 6.16 permettent dagir plus finement, en limitant laugmentation de taille de la base de donnes provoque par la totalit des recommandations, ou le nombre de colonnes proposes dans un index composite. Vous pouvez aussi, sur une dition Entreprise, spcifier que les sugges-

202

Chapitre 6. Utilisation des index

tions dindex doivent loption ONLINE. videmment, comme le DTA ne cre aucun index en ralit, laugmentation de taille provoque par les index suggrs nest quune estimation et peut se rvler diffrente lors de la cration effective des PDS. Lorsque vous avez prpar votre session, vous pouvez lancer le travail en cliquant sur le bouton Start Analysis de la barre de bouton. Une progression est montre dans une nouvelle fentre, avec notamment laffichage du journal (tuning log). Si vous constatez des erreurs ou des avertissements durant lanalyse, vous pouvez la stopper, avec ou sans gnration des recommandations. Le DTA gnre les meilleures recommandations possibles selon les instructions dj analyses la demande darrt. Lorsque lanalyse est termine, deux onglets sont crs. Premirement, recommendations liste les oprations recommandes (cration ou suppression de PDS) et indique une estimation de pourcentage de gain de performances apport par ces changements. La taille estime des index est aussi affiche. Vous pouvez slectionner les oprations choisies, voir le script de cration et le copier dans le pressepapiers (figure 6.17).

Figure 6.17 Gnration du code des recommandations.

Ensuite, longlet report (figure 6.18) offre une vue complte des rsultats de lanalyse, vous permettant de voir en dtail le travail effectu.

6.5 Database Engine Tuning Advisor

203

Figure 6.18 Rapport des recommandations.

Analyse comme si
Le DTA permet une analyse prospective (what-if analysis). Cela veut dire que vous pouvez valuer les diffrences de performances en rapport avec des modifications de PDS que vous prvoyez, par exemple analyser limpact de la prsence dindex, sans les crer rellement. Le DTA ne crera pas les structures physiques, mais il fera comme si , et considrera les structures dans loptimisation des requtes. Vous pouvez ainsi indiquer des index (clustered et nonclustered), vues indexes et des partitionnements dindex. Cela vous permet effectivement de tester diffrentes solutions, comme diffrents partitionnements ou diffrents index clustered, de faon purement hypothtique, et de choisir la meilleure solution. Comme le DTA se base sur loptimiseur SQL pour analyser ses requtes, vous tes assez proche de la vrit dans les rsultats. Cest donc une alternative valable aux tests rels . Cela peut aussi vous permettre de tester les recommandations du DTA pour valuer leur utilit. Pour effectuer une analyse comme si dans une session graphique du DTA, utilisez la commande Import Session Definition... du menu Files, et insrez dans une dfinition de session XML. Voici un exemple de dfinition dun index nonclustered fourni par une recommandation du DTA :
<?xml version="1.0" encoding="utf-16"?>

204

Chapitre 6. Utilisation des index

<Configuration SpecificationMode="Relative"> <Server> <Name>BABALUGA-XPS\SQL2005</Name><Database> <Name>AdventureWorks</Name> <Schema> <Name>Production</Name> <Table> <Name>Product</Name> <Recommendation> <Create> <Index Clustered="false" Unique="false" Online="false" IndexSizeInMB="0.039063"> <Name>_dta_index_Product_5_1429580131__K19_K1_2_3</Name> <Column SortOrder="Ascending" Type="KeyColumn"> <Name>[ProductSubcategoryID]</Name> </Column> <Column SortOrder="Ascending" Type="KeyColumn"> <Name>[ProductID]</Name> </Column> <Column SortOrder="Ascending" Type="IncludedColumn"> <Name>[Name]</Name> </Column> <Column SortOrder="Ascending" Type="IncludedColumn"> <Name>[ProductNumber]</Name> </Column> <FileGroup>[PRIMARY]</FileGroup> </Index> </Create> </Recommendation> </Table> </Schema> </Database></Server> </Configuration>

Vous pouvez faire gnrer cette dfinition automatiquement par le DTA daprs une session danalyse, pour tester les recommandations du DTA slectionnes en mode hypothtique, et donc essayer plusieurs parties des recommandations. Dans votre session, slectionnez la commande Evaluate Recommendations du menu Actions. Une nouvelle session est alors cre, avec linsertion automatique dans la dfinition de session, des PDS rsultant de lanalyse.

Pour comparer les configurations, utilisez loutil en ligne de commande dta.exe en spcifiant le nombre de lignes traiter avec loption -n, cela vous donne la garantie que les charges de travail seront identiques, et donc que vous comparerez bien les mmes choses.

6.5 Database Engine Tuning Advisor

205

Utilisation en ligne de commande


Lexcutable dta.exe vous permet dexcuter une session danalyse en mode ligne de commande. Cela rend le DTA programmable et planifiable. Toutes les options sont exprimables par des paramtres passs lexcutable, ou dans un fichier XML dentre. Voici un exemple dappel en ligne de commande :
dta.exe -S BABALUGA-XPS\SQL2005 -E D AdventureWorks,AdventureWorksDW -d AdventureWorks -s MaSession if workload.sql of rec.sql -fa IDX -A 0 -n 10000

qui signifie : connecte-toi au serveur BABALUGA-XPS\SQL2005 en scurit intgre, dans le contexte de AdventureWorks ; cre la session nomme MaSession pour analyser les bases AdventureWorks et AdventureWorksDW avec le fichier de requtes workload.sql. cris le script de gnration des recommandations dans le fichier rec.sql. Najoute que des index, fais une analyse sans dure limite, mais en ne prenant que les 10 000 premires instructions du workload dentre. Pour interrompre dta.exe avant la fin du travail, utilisez la combinaison de touches CTRL+C. Le DTA peut recevoir ses paramtres dans un fichier XML, mais aussi produire ses recommandations dans un output en format XML (avec le paramtre -ox), pour tre par exemple utilis comme outil doptimisation intgr dans un outil tiers. Vous trouverez la syntaxe dappel complte dans les BOL, ou avec un
dta.exe -?

Analyse sur un serveur de test


Pour raliser une bonne analyse, sans consquence sur le serveur de production, DTA permet de dplacer la charge de lanalyse sur un serveur de test. Il utilise pour ce faire une mthodologie intelligente : une copie automatique de la base est effectue sur le serveur de test, mais uniquement des mtadonnes (structures dobjets) et des statistiques de distribution des colonnes. Les donnes elles-mmes sont inutiles pour le processus doptimisation, et il serait coteux de les rpliquer sur la machine de test. DTA sen passe donc. De plus, pour coller au plus prs de la configuration du serveur de production, il utilise les informations matrielles quil a glanes sur celuici ( laide de la procdure stocke tendue xp_msver) pour fonder son optimisation sur le serveur de test. On profite donc dune analyse au plus prs de la ralit, sans les inconvnients. Pour ce faire, vous devez tre sysadmin sur les deux serveurs, ou excuter lanalyse sous un login prsent sur les deux instances. Vous devez ensuite utiliser un fichier XML, qui spcifie dans llment <TuningOptions><TestServer></TestServer></TuningOptions> le nom du serveur de test, et le passer en paramtre en ligne de commande, dta.exe. Loutil graphique ne supporte pas le travail avec un serveur de test. Ensuite, DTA importe dans une base de donnes de travail, sur le serveur de test, les mtadonnes de la base de production : tables vides, index et objets de code, statistiques de distribution, informations du matriel de production (nombre de processeurs, quantit de mmoire).

7
Transactions et verrous

Objectif
La transaction est le mcanisme de base du maintien de la cohrence dun SGBD client-serveur. Il est donc important de bien le connatre. Loutil mis en place par les transactions pour assurer cette cohrence est le verrouillage. Un verrouillage excessif pnalise les performances. Il est indispensable de matriser ce sujet et de limiter autant que possible ltendue et la dure des transactions, donc du verrouillage, pour viter les attentes trop longues, les blocages, et les treintes fatales (deadlocks).

7.1 DFINITION DUNE TRANSACTION


Un SGBDR est un systme multi-utilisateurs. De ce point de vue, ses responsabilits sont doubles : il doit assurer un accs concurrent aux donnes, et assurer que cellesci restent dans un tat cohrent, par rapport aux contraintes dfinies par la structure de la base, aux modifications densembles de donnes, et aux potentialits dcritures simultanes. Cette protection est assure par le mcanisme de la transaction. Les transactions permettent de contrler la cohrence des donnes et de ne permettre une validation que lorsque celle-ci est respecte. Lors de toute modification, la base est dans un tat incohrent durant un laps de temps plus ou moins long. Par exemple, si nous voulons mettre tous les noms de famille de la table Person.Contact en majuscules, nous excutons le code suivant :
UPDATE Person.Contact SET LastName = UPPER(LastName);

208

Chapitre 7. Transactions et verrous

qui met jour prs de 20 000 lignes. Que se passe-t-il si une erreur se produit au milieu de cette modification, ou si, en mme temps, une autre session change le nom dun contact ? Du point de vue du SGBDR, ce sont des incohrences. Pour assurer que cela ne se produise pas, toute modification (insertion, suppression, mise jour) de donnes est obligatoirement protge par un mcanisme transactionnel qui garantit, travers quatre critres, la consistance de cette modification. Ces critres sont nomms lacidit de la transaction, par leur acronyme (ACID). Une transaction doit tre : Atomique Une transaction est atomique, parce quelle reprsente une seule unit de modification, mme si la mise jour porte sur plusieurs milliers de lignes, ou plusieurs tables. Cela signifie quune telle modification sera totalement valide, ou totalement annule. La base de donnes ne restera jamais dans un tat intermdiaire, o une partie seulement des modifications est enregistre. Cohrente Elle est cohrente, parce quelle ne peut violer les contraintes de la base de donnes, ou laisser la base dans un tat intermdiaire. Si dans une mise jour de cent lignes, une ligne viole une contrainte dintgrit rfrentielle (une cl trangre), la transaction sera totalement annule. La base passe dun tat cohrent avant la transaction, un tat cohrent aprs validation. Isole La transaction sexcute dans un contexte disolation vis--vis des autres transactions. Les lignes touches par une modification sont protges contre un accs par une autre transaction, que ce soit en lecture (selon le niveau disolation, nous le verrons), ou surtout, en criture. Durable Lorsque la transaction est valide, elle est durable, cest--dire que les modifications sont dfinitivement inscrites dans la base de donnes, sauf dfaillance matrielle du disque, bien entendu. Pour illustrer lutilit de cette acidit, imaginez-vous une partie de billard. Lorsque cest votre tour de jouer, vous restez la table tant que vous placez une bille dans une poche. Quand vous avez fini, vos points sont enregistrs, on ne peut revenir en arrire. Votre tour est donc atomique et durable. Vous ne pouvez violer les rgles du jeu, sinon votre action est nulle, cest donc consistant. Et, le plus important, personne ne peut jouer en mme temps que vous : lorsque cest votre tour, personne dautre ne touche les billes sur la table, sinon, le jeu ne signifie plus rien. La durabilit et latomicit sont possibles grce au journal de transactions, qui permet de rejouer ou dannuler une transaction, quelle que soit sa dure. Lisolation, qui est lattribut qui garantit la cohrence des modifications tout en diminuant la concurrence, est gre travers le mcanisme de verrouillage. Toute instruction DML de modification de donnes (INSERT, UPDATE, DELETE, MERGE) est encapsule dans une transaction. Un seul UPDATE par exemple, quel que soit le nombre de lignes quil affecte, est donc ACID. Le SELECT ne gnre aucune transaction. Pour enrler plusieurs instructions dans une seule transaction, et donc

7.2 Verrouillage

209

les rendre ACID, vous devez dclarer une transaction utilisateur explicite, laide des instructions TCL (Transaction Control Language) du langage SQL : BEGIN TRANSACTION dbute une transaction explicite ; SAVE TRANSACTION introduit un point de sauvegarde. On pourra ensuite valider ou annuler la partie de la transaction jusqu ce point ; COMMIT TRANSACTION valide la transaction et la termine ; ROLLBACK TRANSACTION annule la transaction et la termine. Ces commandes permettent donc dtendre les principes dACIDit de la transaction plusieurs instructions DML ou DDL (en SQL Server, les instructions DDL sont transactionnelles). Dans les faits, cela transforme une suite dordres SQL en un seul ordre logique.

Validation automatique et annulation automatique En SQL Server, le terme transaction implicite est utilis pour dcrire un fonctionnement connu sous le nom de validation automatique (autocommit). Par dfaut, une session est en mode autocommit, cest--dire quune instruction DML transactionnelle est automatiquement valide ou annule (si elle provoque une exception) aprs excution. Vous pouvez changer ce mode laide de la commande SET IMPLICIT_TRANSACTIONS { ON | OFF }. En mode IMPLICIT_TRANSACTIONS ON, toute instruction doit valider ou annuler la transaction par un COMMIT ou un ROLLBACK. Ce mode est dangereux et en gnral inutile, ne lutilisez donc pas. De mme, loption SET XACT_ABORT { ON | OFF } permet dannuler automatiquement une transaction explicite lorsquune instruction provoque une exception. Elle est active par dfaut. Elle influe sur la valeur de la fonction XACT_STATE() qui peut tre teste, en gnral dans la partie CATCH dun bloc TRY CATCH, pour connatre ltat de la transaction. Aprs une exception, si XACT_ABORT est OFF, XACT_STATE() vaudra 1 (transaction active et validable. Si elle est ON, XACT_STATE() vaudra -1 (transaction active et en erreur). Lorsque XACT_STATE() vaut -1, seul un ROLLBACK est possible. Dans la pratique, il est rare de vouloir grer une validation partielle de la transaction, cela peut provoquer plus facilement de la confusion que des fonctionnalits utiles.

7.2 VERROUILLAGE
Le principe de la transaction qui nous intresse plus particulirement en regard des performances, est lisolation. Il consiste assurer quune seule transaction la fois puisse accder aux mmes donnes. Cette isolation est ralise travers le mcanisme de verrouillage. Ds quune transaction accde une ressource, celle-ci est verrouille (le moteur de stockage pose un verrou sur lobjet : la ligne, la page ou la table), afin de la protger des accs concurrents. Il existe plusieurs types de verrous (ou modes de verrouillage), qui sont compatibles ou non entre eux, et plusieurs granularits. Nous allons les passer en revue.

210

Chapitre 7. Transactions et verrous

7.2.1 Modes de verrouillage


Vous trouvez dans le tableau 7.1 une liste des types de verrous les plus frquemment rencontrs dans SQL Server.
Tableau 7.1 Les modes de verrouillage Mode S X U IS IX Sch-S Nom Shared Exclusive Update Intent Shared Intent Exclusive Schema Shared Verrou partag Verrou exclusif : une criture est en cours sur la ressource verrouille. Verrou de mise jour Verrou dintention partag. Un verrou partag (S) est pos une granularit plus fine. Verrou dintention exclusif. Un verrou exclusif (X) est pos une granularit plus fine. Verrou partag de schma : lors de lutilisation dun objet, ce verrou empche une modification de la structure de cet objet. Description

Certains verrous sont mutuellement compatibles : deux verrous compatibles peuvent tre simultanment poss sur la mme ressource. Les verrous incompatibles sont mutuellement exclusifs. Le tableau 7.2 prsente la compatibilit des principaux modes de verrouillage.
Tableau 7.2 Compatibilit des verrous Mode S X U IS IX SIX S oui non oui oui non non X non non non non non non U oui non non oui non non IS oui non oui oui oui oui IX non non non oui oui non SIX non non non oui non non

Les verrous partags (shared, S) sont des verrous de lecture. Ils sont poss lors de tout SELECT, dans les niveaux disolation partir de READ COMMITTED (voir section 7.3). Comme leur nom lindique, ils sont compatibles entre eux. Vous pouvez donc poser des verrous partags sur des ressources qui en ont dj, et ainsi lire en

7.2 Verrouillage

211

mme temps les mmes lignes. Par contre, ils permettent de protger les oprations de lecture contre lcriture simultane par une autre transaction, ce qui pourrait dboucher sur des lectures sales. Ils sont incompatibles avec les verrous dcriture (X), ce qui permet, dans lautre sens, de prvenir une lecture sur une ressource en cours de modification. Prenons la requte suivante, qui met jour la table den-ttes de commandes pour ajouter dix jours la date denvoi, puis ajoute dans la mme transaction dix jours la date limite de paiement dans la table de dtail de commandes :
BEGIN TRAN BEGIN TRY UPDATE Purchasing.PurchaseOrderHeader SET ShipDate = DATEADD(day, 10, ShipDate) UPDATE Purchasing.PurchaseOrderDetail SET DueDate = DATEADD(day, 10, DueDate) COMMIT TRAN END TRY BEGIN CATCH IF (XACT_STATE()) <> 0 ROLLBACK TRAN END CATCH

Si une lecture a lieu entre les deux instructions, elle peut trouver des dates de paiement antrieures aux dates denvoi, ce qui est une incohrence fonctionnelle. Pour viter cela, une lecture ne peut se faire que lorsquun verrou S est pos sur les ressources. Ce verrou tant incompatible avec le verrou X, il doit attendre la libration des verrous X des ressources souhaites. Les verrous de type exclusifs (exclusive, X) sont des verrous dcriture, poss sur des lignes en cours de modification (INSERT, DELETE, UPDATE). Ils sont incompatibles avec tout type de verrou, y compris eux-mmes : quelles que soient les options de la base ou de la session, il est interdit deux transactions dcrire simultanment les mmes ressources. Si cela tait possible, ce serait le chaos1. On ne peut poser ni verrou exclusif, ni verrou partag, etc., sur une ressource qui possde dj un verrou exclusif. On ne peut donc pas non plus lire une ligne en cours de modification, dans un niveau disolation qui pose un verrou partag la lecture. Les verrous de mise jour (update, U) sont poss dans les niveaux disolation qui maintiennent les verrous partags toute la dure de la transaction (REPEATABLE READ et SERIALIZABLE), ils permettent dviter une forme courante de verrou mortel (deadlock), lorsque la lecture sert, dans une instruction suivante, aux mises jour. Dans ce cas, le verrou partag doit tre converti en verrou exclusif. Mais, si une autre transaction maintient elle aussi des verrous partags sur ces ressources, la premire doit
1. Il existe sur certains SGBDR un niveau disolation de la transaction nomm CHAOS, qui permet les critures simultanes. SQL Server ne le supporte pas. Le niveau disolation minimal est READ UNCOMMITTED, comme nous le verrons plus loin.

212

Chapitre 7. Transactions et verrous

attendre leur libration. Lorsque la seconde transaction essaie son tour de modifier les donnes, elle doit galement attendre sur la premire, do situation inextricable et deadlock (galement nomm interblocage ou verrou mortel). Pour viter cela, SQL Server pose galement un verrou de mise jour. Un seul verrou U peut-tre pos dans le mme temps, sur la mme ressource. Cela permet la transaction de convertir le verrou S en verrou X sans attendre, si elle possde le verrou U. Les verrous intentionnels (intent, I) permettent de protger les ressources aux granularits suprieures, contre un verrouillage incompatible. Si une transaction pose un verrou sur une ligne, il est ncessaire dempcher les autres transactions de poser des verrous sur la page qui contient cette ligne, ou sur la table, afin de ne pas se retrouver avec des verrous incompatibles par une voie dtourne. Les verrous dintention permettent doptimiser les performances de verrouillage, en vitant la recherche des verrous de granularit plus fine. Si une transaction veut verrouiller la table, elle na pas besoin de chercher sil existe des verrous de ligne ou de page, il lui suffit de vrifier la prsence de verrous effectifs ou dintention, sur la table. Prenons un exemple, excutons une requte de modification sur une table dAdventureWorks :
USE AdventureWorks GO BEGIN TRAN UPDATE TOP (1) Sales.Currency SET ModifiedDate = current_timestamp

Examinons les verrous :


SELECT tl.resource_type, tl.resource_subtype, DB_NAME(tl.resource_database_id) as db, tl.resource_description, CASE tl.resource_type WHEN 'OBJECT' THEN OBJECT_NAME(tl.resource_associated_entity_id) ELSE COALESCE(OBJECT_NAME(p.object_id), CAST(tl.resource_associated_entity_id as sysname)) END as obj, tl.request_mode, tl.request_status FROM sys.dm_tran_locks tl LEFT JOIN sys.partitions p ON tl.resource_associated_entity_id = p.hobt_id WHERE tl.request_session_id = @@SPID AND tl.resource_database_id = DB_ID() ORDER BY CASE resource_type WHEN 'KEY' THEN 1 WHEN 'RID' THEN 1 WHEN 'PAGE' THEN 2

7.2 Verrouillage

213

WHEN WHEN WHEN WHEN WHEN WHEN ELSE END

'EXTENT' THEN 3 'HOBT' THEN 4 'ALLOCATION_UNIT' THEN 5 'OBJECT' THEN 6 'DATABASE' THEN 7 'FILE' THEN 8 9

Le rsultat est visible sur la figure 7.1. Nous voyons que des verrous dintention exclusifs ont t placs sur la page, et sur la table.

Figure 7.1 Verrous

Les verrous intentionnels comportent le mode du verrou originel : IS pour Intent Shared, IX pour Intent Exclusive, etc. Les verrous de schma (schema, Sch-M) sont des verrous sur les structures. Il serait dsagrable de voir la table dans laquelle on est en train de lire, supprime par une autre transaction. Les verrous dtendue (range, RangeM-M) sont des verrous sur les ressources qui entourent la ressource verrouille. Elles permettent de prvenir les lectures fantmes en niveau disolation SERIALIZABLE. Nous en parlerons quand nous prsenterons les niveaux disolation.

Nous pouvons observer le verrouillage travers plusieurs vues de gestion dynamiques que nous prsenterons au long de ce chapitre. La vue principale affichant les verrous en cours est sys.dm_tran_locks.

propos des latches


Vous verrez parfois, par exemple dans les sessions en attente (en excutant la vue sys.dm_os_waiting_tasks, notamment), passer le mot latch, par exemple avec des attentes de type PAGEIOLATCH. Les latches, traduits en franais dans la documentation par verrous internes ou verrous de ressources internes, sont des verrous de synchronisation, lgers, rapides et poss habituellement durant un temps trs court, destins protger les structures physiques lors de leur accs par le moteur de stockage et leur transmission au moteur

214

Chapitre 7. Transactions et verrous

relationnel. Lorsquune ligne est lue, par exemple, SQL Server doit protger non seulement la ligne, mais galement la page (pour viter quune insertion ne modifie len-tte qui indique loffset de la ligne). Les latches ne sont maintenus que sur des pages de donnes prsentes dans le buffer (alors que des verrous peuvent rester poss sur des pages sur le disque), car toute page en cours dutilisation physique est place dans le buffer. Cela aide ainsi garantir une grande rapidit du latch.

Il existe aussi dautres types de latches non-buffer, sur des structures internes.

Vous pouvez obtenir des statistiques sur la gestion des latches avec lobjet de compteur de performances SQL:Latches. Les latches dentres/sorties sont des types particuliers de latches de buffer, utiliss pour la synchronisation entre les pages du buffer et les pages du disque, on les voit dans les attentes de type PAGEIOLATCH. Ces attentes sont souvent typiques des latences du disque. Vous pouvez utiliser une requte comme celle-ci pour dtecter ces attentes :
SELECT wait_type, waiting_tasks_count, wait_time_ms FROM sys.dm_os_wait_stats WHERE wait_type like 'PAGEIOLATCH%' ORDER BY wait_type

7.2.2 Granularit de verrouillage


Les verrous sont poss sur des ressources. Les verrous de donnes (il existe des verrous poss sur des objets de code, pour viter leur modification durant lexcution) peuvent concerner les ressources suivantes : la ligne (ou la cl dindex), la page, la table, et la base de donnes. On parle de granularit pour indiquer quel niveau de ressource seffectue le verrouillage. En SQL Server, la granularit par dfaut est la ligne, ou la cl dindex. Cela permet de verrouiller le moins de ressources possible et offre donc un excellent niveau de concurrence. Par exemple, lors dinsertions, il prvient la contention sur la page qui se produit sur les tables clustered dans les SGBDR dont la granularit minimale est la page. Cette granularit comporte nanmoins un dsavantage : si la commande affecte un grand nombre de lignes, cela implique de poser un grand nombre de verrous. Chaque verrou cote du temps processeur et de la mmoire vive. Pour limiter les cots, SQL Server value le nombre de lignes traiter, et lors de lexcution, peut convertir des verrous en granularit plus large. Pour saler le contenu de votre assiette, vous prenez une pince. Si vous devez saler la marmite dun rgiment, vous versez partir dun grand sachet de sel, car le faire pince par pince vous prendrait des heures.

7.2 Verrouillage

215

Par exemple, au lieu de poser 19 972 verrous sur la table Person.Contact pour modifier toutes les lignes, il ne va poser quun seul verrou, sur la table. Cette conversion sappelle lescalade. Elle fait lconomie dun verrouillage intense, mais elle bloque aussi potentiellement plus de ressources que ncessaire, et donc diminue la concurrence daccs.

Escalade de verrous
Vous pouvez obtenir des informations prcises sur le verrouillage de vos index par la fonction sys.dm_db_index_operational_stats(). Notamment, la colonne index_lock_promotion_count indique le nombre descalades :
SELECT OBJECT_NAME(ios.object_id) as table_name, i.name as index_name, i.type_desc as index_type, row_lock_count, row_lock_wait_count, row_lock_wait_in_ms, page_lock_count, page_lock_wait_count, page_lock_wait_in_ms, index_lock_promotion_attempt_count, index_lock_promotion_count FROM sys.dm_db_index_operational_stats( DB_ID('AdventureWorks'), OBJECT_ID('Person.Contact'), NULL, NULL) ios JOIN sys.indexes i ON ios.object_id = i.object_id AND ios.index_id = i.index_id ORDER BY ios.index_id;

Parfois, vous voudrez contrler le niveau de granularit, soit en laugmentant pour augmenter les performances (cest trs rarement ncessaire), soit pour empcher la pose de verrous aux granularits suprieures, qui bloquent dautres transactions. La deuxime option est plus courante, dans des cas de mises jour dune grande partie ou de la totalit dune table, par exemple. Lopration prend du temps, et si un verrou est pos sur la table, il bloque tout autre travail sur celle-ci.

Augmentation de la granularit
Nous verrons dans la section 8.2.1 que nous pouvons contrler la granularit de verrouillage par table. Nous avons aussi la possibilit de contrler le verrouillage de tous les accs aux tables clustered travers les options de cration dindex ALLOW_ROW_LOCKS et ALLOW_PAGE_LOCKS. Nous les avons survols en section 6.1.3. ALLOW_ROW_LOCKS = OFF dsactive le verrouillage de lignes, ALLOW_PAGE_LOCKS = OFF dsactive le verrouillage de page. Appliqus sur un index clustered, ils grent effectivement la granularit de verrouillage de la table. Ils sont trs rarement utiles. Vous pouvez les essayer avec lexemple de code suivant, qui prsente deux requtes utiles : la premire retourne les index dont ces options ont t modifies, la seconde permet de tracer les verrous poss par un spid laide des vues de gestion dynamique :

216

Chapitre 7. Transactions et verrous

SELECT OBJECT_NAME(object_id) as table_name, name as index_name, allow_row_locks, allow_page_locks FROM sys.indexes WHERE allow_row_locks & allow_page_locks = 0 ORDER BY table_name, index_name; ALTER INDEX PK_Contact_ContactID ON Person.Contact SET (ALLOW_ROW_LOCKS = OFF, ALLOW_PAGE_LOCKS = OFF); SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT LastName, FirstName, EmailAddress FROM Person.Contact WHERE LastName = 'Adams'; SELECT CASE es.transaction_isolation_level WHEN 0 THEN 'non spcifi' WHEN 1 THEN 'READ UNCOMMITTED' WHEN 2 THEN 'READ COMMITTED' WHEN 3 THEN 'REPEATABLE' WHEN 4 THEN 'SERIALIZABLE' WHEN 5 THEN 'SNAPSHOT' END as transaction_isolation_level, tl.request_session_id as spid, tl.resource_type, tl.request_mode, tl.request_type, CASE WHEN tl.resource_type = 'object' THEN OBJECT_NAME(tl.resource_associated_entity_id) WHEN tl.resource_type = 'database' THEN DB_NAME(tl.resource_associated_entity_id) WHEN tl.resource_type IN ('key','page') THEN (SELECT object_name(i.object_id) + '.' + i.name FROM sys.partitions p JOIN sys.indexes i ON p.object_id = i.object_id AND p.index_id = i.index_id WHERE p.hobt_id = tl.resource_associated_entity_id) ELSE CAST(tl.resource_associated_entity_id as varchar(20)) END as objet FROM sys.dm_tran_locks tl LEFT JOIN sys.dm_exec_sessions es ON tl.request_session_id = es.session_id WHERE request_session_id = @@spid ORDER BY CASE resource_type WHEN 'DATABASE' THEN 10 WHEN 'METADATA' THEN 20 WHEN 'OBJECT' THEN 30 WHEN 'PAGE' THEN 40 WHEN 'KEY' THEN 50

7.2 Verrouillage

217

ELSE 100 END ROLLBACK ALTER INDEX PK_Contact_ContactID ON Person.Contact SET (ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);

En modifiant les options dindex, vous observerez les diffrences de verrouillage.

Diminution de la granularit
Vous pourriez parfois avoir besoin dempcher lescalade de verrous, notamment sur les requtes de modification qui touchent une grande partie de la table. Vous avez pour cela deux moyens. Le plus propre est de dcouper votre requte de modification en tapes, pour ne toucher quune partie de la table chaque fois. laide de la requte liste plus haut, vous pouvez voir la diffrence de verrouillage de ces deux instructions :
BEGIN TRAN UPDATE Person.Contact SET LastName = UPPER(LastName); UPDATE TOP (1000) Person.Contact SET LastName = UPPER(LastName); ROLLBACK

La premire pose un verrou exclusif sur la table, et un UPDATE Person.Contact WITH (ROWLOCK) ny change rien, car il naffecte que le plan dexcution, pas le comportement descalade lors de lexcution mme. La seconde, par contre, pose des verrous sur les lignes (chaque cl de lindex clustered. Ainsi, vous pourriez crer une boucle WHILE de ce type :
DECLARE @incr int, @rowcnt int SET @incr = 1 SET @rowcnt = 1 WHILE @rowcnt > 0 BEGIN UPDATE TOP (1000) Person.Contact SET LastName = UPPER(LastName) WHERE ContactID >= @incr SET @rowcnt = @@ROWCOUNT SET @incr = @incr + 1000 END

Une seconde solution, relevant plus du bricolage, existe. Elle se base sur le fait quun verrou dintention pos un niveau de granularit suprieur nempche pas les verrous de granularit infrieure, et prvient bien sr la pose de verrous incompatibles. Vous pouvez poser un verrou dintention exclusif sur la table, par exemple de cette manire :

218

Chapitre 7. Transactions et verrous

BEGIN TRAN SELECT TOP (0) * FROM Person.Contact WITH (UPDLOCK, HOLDLOCK)

Le verrou reste pos jusquau ROLLBACK ou COMMIT de la transaction. Vous pourriez crer un travail de lagent qui lance la commande, avec un WAITFOR pour limiter la dure de la transaction (attention la taille du journal de transactions !). Tant que cette transaction est ouverte, les autres transactions ne pourront pas escalader leurs verrous sur la table. Elles essaieront rgulirement durant lexcution, et vous verrez leurs essais dans la colonne index_lock_promotion_attempt_count de la fonction sys.dm_db_index_operational_stats(). Les lectures et critures se feront sans problme.

Vous pouvez totalement dsactiver lescalade sur votre serveur laide du drapeau de trace 1211 : DBCC TRACEON (1211, -1) ou -T1211 au dmarrage de linstance. Cest une solution vraiment dconseille, vous verrez sans aucun doute une baisse gnrale de performances due un nombre de verrous excessif.

Vous retrouvez des conseils prcis dans larticle de la base de connaissance 323630 : http://support.microsoft.com/kb/323630

Configurer lescalade de verrous sur les tables partitionnes, en SQL Server 2008 En SQL Server 2005, il nexiste pas de granularit de verrou par partition. Lescalade ne peut se faire que de la page la table, mme si la requte na besoin daccder qu une seule partition. En SQL Server 2008, la partition devient une granularit de verrouillage possible. Vous pouvez grer finement cette possibilit table par table si vous le souhaitez. La commande ALTER TABLE a t enrichie de loption : SET ( LOCK_ESCALATION = { AUTO | TABLE | DISABLE } ). Voici un exemple de syntaxe : ALTER TABLE dbo.matable SET (LOCK_ESCALATION = AUTO) AUTO permet la slection automatique de la granularit des verrous. Sur une table partitionne, lescalade pourra se produire sur la partition. Sur une table non partitionne, lescalade se produira sur la table. TABLE lescalade se fera sur la table, quelle soit partitionne ou non. Il sagit de la valeur par dfaut, et correspondant au comportement de SQL Server 2005. DISABLE prvient lescalade dans la plupart des cas. Le verrouillage de table nest pas compltement dsactiv.

7.3 NIVEAUX DISOLATION DE LA TRANSACTION


Nous lavons vu, lisolation de la transaction, travers le mcanisme de verrouillage, a une forte influence sur la cohrence des donnes. Comme toute application multiutilisateurs, SQL Server doit quilibrer son comportement entre le maintien de cette cohrence, et le besoin de permettre des accs concurrents. Ce sont deux exigences

7.3 Niveaux disolation de la transaction

219

opposes. Plus la cohrence est garantie, plus le nombre possible daccs concurrents est faible, et vice-versa. Cela a donc fort un impact sur les performances. Afin de contrler cet quilibre, nous pouvons agir sur lisolation, en modifiant le niveau disolation de la transaction, dans le contexte de la session, ou par table requte. La norme SQL dfinit quatre niveaux disolation, qui rpondent quatre problmatiques connues de la cohrence transactionnelle : les mises jour perdues, les lectures sales, les lectures non renouvelables, et les lectures fantmes. La mise jour perdue (lost update) se produit lorsque deux transactions lisent puis modifient simultanment les mmes donnes (les mmes lignes) partir de cette lecture. La dernire mise jour tant celle qui est prise en compte, des modifications appliques par les transactions prcdentes peuvent tre crases. La lecture sale (dirty read) dcrit le cas o une ligne est lue alors quelle est en cours de modification par une autre transaction, soit parce que linstruction est en cours dexcution, soit parce que la transaction qui a modifi la ligne nest pas encore valide (ce qui selon le principe de latomicit de la transaction est strictement quivalent). Le risque encouru est donc dobtenir des informations incompltes ou non valides. La lecture non renouvelable (non-repeatable read) indique la possibilit, pour une transaction, de trouver des valeurs diffrentes dans les lignes lues deux moments de la transaction, parce quune autre transaction aura modifi les donnes entre les deux lectures. Cest un danger pour les transactions qui comptent sur la cohrence de ces donnes dans la dure de vie de la transaction pour leur traitement. La lecture fantme (phantom read) est forme aggrave de la lecture non rptable. Elle dcrit lapparition de nouvelles lignes lors de nouvelles lectures, lorsque dans la mme transaction, un deuxime SELECT portant sur les mmes lignes (la mme clause WHERE), voit apparatre de nouvelles lignes, parce quune autre transaction aura insr des donnes rpondant la clause de filtre. Rpondre ces diffrents problmes implique, pour une transaction, de choisir diffrentes stratgies de verrouillage. Ces stratgies sont dictes par le niveau disolation de la transaction. La norme SQL dcrit quatre niveaux disolation, qui rpondent chacun un problme transactionnel. Ce sont, dans lordre croissant de cohrence : READ UNCOMMITTED ; READ COMMITTED ; REPEATABLE READ ; SERIALIZABLE.

SQL Server, partir de la version 2005, ajoute un niveau disolation propritaire, bas sur le row versioning, appel SNAPSHOT.

220

Chapitre 7. Transactions et verrous

Ces niveaux disolation permettent de choisir un quilibre entre la cohrence des donnes et la concurrence daccs : plus la cohrence transactionnelle est maintenue par un verrouillage fort, plus les donnes sont isoles des autres transactions, et donc moins la concurrence daccs simultane entre sessions est possible. Le niveau disolation peut tre chang pour la session laide de linstruction SET TRANCATION ISOLATION LEVEL comme nous le verrons ci-aprs. Il peut aussi tre configur dans une mme requte, table par table, laide dun indicateur de table, tout comme on peut choisir une granularit de verrou :
SELECT * FROM Person.Contact WITH (READUNCOMMITTED); -- ou SELECT * FROM Person.Contact WITH (NOLOCK);

Les deux syntaxes prcdentes sont strictement quivalentes. NOLOCK est simplement un alias propre SQL Server pour indiquer un niveau READ UNCOMMITTED. Mais voyons ces niveaux. Le niveau disolation READ UNCOMMITTED est le niveau le plus permissif, posant le moins de verrou, et permettant donc lapparition du plus grand nombre derreurs transactionnelles. Dans ce niveau, les lectures sales (dirty reads) sont possibles : une transaction peut lire des lignes qui sont en train dtre modifies par une autre transaction, ou modifier des lignes en cours de lecture. Lextraction de donnes non valides ( sales ) est donc possible. Dans ce niveau, SQL Server continue poser des verrous exclusifs (X) lors de modifications : il est toujours impossible dcrire en mme temps les mmes lignes. Par contre, en lecture, aucun verrou nest pos. Seul un verrou de schma (Sch-S) est maintenu, empchant la modification de la structure des tables lues. De mme, les verrous exclusifs ne sont pas honors par la lecture. Ce niveau est donc trs favorable aux performances, en diminuant les oprations de verrouillage, et les attentes de libration de verrous. Vous entendrez certainement des mises en garde contre le niveau disolation READ UNCOMMITTED. Les lectures sales inquitent certaines personnes. Dans la pratique, pour des gnrations de rapport, de lextraction de donnes ou du simple affichage, il est rare de se proccuper dobtenir une ligne valide ou non. un centime de secondes prs, vous obtenez une valeur diffrente, quelle importance ? Compte tenu du gain de performances apport par ce mode, nous ne pouvons que vous le conseiller pour des requtes de lecture. La vraie contre-indication se prsente dans un environnement o des transactions modifient plusieurs lignes en rapport les unes avec les autres, dans des tables diffrentes ou dans la mme table. Exemple classique, si vous grez une application bancaire et que vous effectuez par SELECT un total des avoirs dun client, vous risquez, en niveau READ UNCOMMITTED, de lire des totaux errons si en mme temps une transaction retire un montant dun compte courant pour le poser sur un compte dpargne. Si le SELECT intervient lorsque la ligne est supprime du compte courant, mais pas encore ajoute au compte dpargne, le total sera infrieur au crdit du client. Dans ce genre de cas, le niveau disolation SNAPSHOT peut permettre une plus grande concurrence, sans ralentir les requtes par des attentes de verrous. Lorsque ce risque nest pas prsent, un SET TRANSACTION ISOLATION

7.3 Niveaux disolation de la transaction

221

LEVEL READ UNCOMMITTED en dbut de procdure stocke ddie la lecture ou au reporting, est une faon simple daugmenter la concurrence et les performances. Noubliez pas de laisser la session dans son niveau par dfaut la fin de la procdure, avec un SET TRANSACTION ISOLATION LEVEL READ COMMITTED. Le niveau disolation READ COMMITTED est le niveau par dfaut dune session SQL Server. Il protge la transaction contre les lectures sales, en ne permettant des lectures que sur les lignes valides. Pour cela, SQL Server pose un verrou partag sur les donnes en cours de lecture. Les verrous partags sont compatibles entre eux (ce qui veut dire quon peut en placer plusieurs sur la mme ressource), mais incompatibles avec les verrous exclusifs. Un verrou S ne pourra donc pas tre plac sur une ligne dj dote dun verrou X. La lecture dune ligne en cours de modification devra donc attendre. Rciproquement, une tentative dcriture devra attendre la libration de tous les verrous partags sur la ressource. Ce niveau est un peu plus contraignant que le premier, et limite un peu plus les performances, en gnrant des verrous partags, et des attentes en lecture. Par contre, il ne protge pas des lectures non renouvelables, car les verrous partags sont librs ds la fin de linstruction de lecture, et ne persistent pas jusqu la fin de la transaction. Le niveau disolation REPEATABLE READ, comme son nom lindique, permet les lectures renouvelables, en maintenant les verrous partags jusqu la fin de la transaction. Faisons le test :
USE AdventureWorks GO SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRAN SELECT TOP 10 * FROM Sales.Currency

En excutant notre requte sur la vue sys.dm_tran_locks, nous trouvons le rsultat de la figure 7.2.

222

Chapitre 7. Transactions et verrous

Figure 7.2 Verrous partags en REPEATABLE READ

Des verrous partags (S) sont maintenus sur chaque cl de lindex clustered (KEY), cest--dire sur chaque ligne impacte dans la requte. Ces lignes ne pourront donc pas tre modifies tant que la transaction est encore active. Nous voyons galement que des verrous dintention partags sont maintenus sur la page, et sur la table (OBJECT). Ainsi, aucune autre session ne pourra modifier ces lignes jusqu la fin de la transaction. cause du verrouillage plus important, nutilisez ce mode que lorsque vous souhaitez, dans une transaction, utiliser le rsultat de votre lecture pour des mises jour dans les instructions suivantes. vitez galement dcrire du code transactionnel qui sexcute dans une logique inverse (par exemple SELECT puis UPDATE dans une transaction, UPDATE puis SELECT dans une autre), car vous vous exposeriez un risque important de deadlock. Lorsque vous le faites dans le mme ordre, SQL Server pose des verrous de mise jour potentielle (update, U) pour viter le problme. Le niveau disolation SERIALIZABLE reprsente la protection maximale contre les erreurs transactionnelles, mais la concurrence daccs minimale. Il protge la transaction contre les lectures fantmes, cest--dire lapparition de lignes supplmentaires lors de plusieurs lectures des mmes donnes. Pour cela, SQL Server doit verrouiller non seulement les lignes lues, mais galement les alentours de la ligne, pour empcher linsertion de donnes correspondant au filtre de la clause WHERE. La nature du verrouillage dpend de la structure de la table. Si la clause WHERE correspond une recherche sur une colonne avec contrainte dunicit (cl primaire, cl unique, index unique), aucun verrou supplmentaire ne sera pos, puisquaucune ligne ne pourra tre ajoute qui correspondra la clause WHERE. Par contre, dans les autres cas, SQL Server posera soit un verrou dtendue de cls (key range), soit, lorsque ncessaire, un verrou sur la table entire. Les verrous dtendues de cls ne se rencontrent quen niveau SERIALIZABLE, et ne sappliquent quaux index (index clus-

7.3 Niveaux disolation de la transaction

223

tered et nonclustered). Ils ont deux composants : une partie tendue, et une partie ligne. Leur mode, visible par exemple dans la colonne request_mode de la vue sys.dm_tran_locks, est crit de cette faon : tendue-ligne . Par exemple, un verrou de ressource partag et dtendue partage sera crit RangeS-S , un verrou de ressource exclusif avec verrou dtendue exclusif, RangeX-X . La liste est disponible dans les BOL, sous lentre Verrouillage dtendues de cls (Key-Range Locking). Sur une table sans index clustered, et dont les colonnes de la clause WHERE ne sont pas indexes, SQL Server sera oblig de poser un verrou de table. Dmonstration :
USE tempdb GO CREATE TABLE dbo.testSerializable (nombre int, texte varchar(50) COLLATE French_BIN) GO INSERT INSERT INSERT INSERT GO INTO INTO INTO INTO dbo.testSerializable dbo.testSerializable dbo.testSerializable dbo.testSerializable VALUES VALUES VALUES VALUES (1, (2, (3, (4, 'un') 'deux') 'trois') 'quatre')

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRAN -- sans index UPDATE dbo.testSerializable SET texte = 'DEUX' WHERE nombre = 2 -- requte sur sys.dm_tran_locks ROLLBACK GO CREATE NONCLUSTERED INDEX nix$testSerializable$nombre ON dbo.testSerializable (nombre) GO BEGIN TRAN -- avec index UPDATE dbo.testSerializable SET texte = 'DEUX' WHERE nombre = 2 -- requte sur sys.dm_tran_locks ROLLBACK GO

224

Chapitre 7. Transactions et verrous

Les deux requtes sur la vue sys.dm_tran_locks ne sont pas incluses dans le code pour des raisons de place, vous les connaissez. Leur rsultat est visible dans la figure 7.3 pour le premier cas (heap sans index), et dans la figure 7.4 pour le second cas (heap avec index nonclustered).

Figure 7.3 Verrous sur un heap

Figure 7.4 Verrous sur une table clustered

Vous pouvez voir que dans le premier cas SQL Server na dautre choix que de poser un verrou exclusif sur la table. Par contre, lorsquun index existe, il peut poser des verrous dtendues sur les cls de lindex qui devraient tre accdes si se produit un ajout de ligne rpondant la clause nombre = 2. Les verrous sont des RangeS-U : partag sur ltendue, mise jour sur la ressource. Un Intent Update est pos sur la page de lindex. Nutilisez le niveau SERIALIZABLE quen cas dabsolue ncessit (changement ou calcul de cl, par exemple), et ny restez que le temps ncessaire. Le verrouillage important entrane une relle baisse de concurrence. Le niveau disolation SNAPSHOT existe depuis SQL Server 2005. Il permet de diminuer le verrouillage, tout en offrant une cohrence de lecture dans la transaction. Alors que dans le niveau READ UNCOMMITTED, des lectures sales restent possibles, le niveau SNAPSHOT permet de lire les donnes en cours de modification, dans ltat cohrent dans lequel elles se trouvaient avant modification. Pour cela, SQL Server utilise une fonctionnalit interne appele version de lignes (row versioning). Pour pouvoir lutiliser, le support du niveau disolation SNAPSHOT doit avoir t activ dans la base de donnes. Exemple :
ALTER DATABASE sandbox SET ALLOW_SNAPSHOT_ISOLATION ON

Quand ceci est fait, toutes les transactions qui modifient des donnes dans cette base, maintiennent une copie (une version) de ces donnes avant modification, dans tempdb, quel que soit leur niveau disolation. Si une autre session en niveau disolation SNAPSHOT essaie de lire ces lignes, elle lira la copie des lignes, et non pas

7.3 Niveaux disolation de la transaction

225

les lignes en cours de modification, ce qui lui garantit une lecture cohrente de ltat de ces donnes, avant modification. Exprimentons-le :
USE sandbox; GO CREATE TABLE dbo.testSnapshot (nombre int, texte varchar(50) COLLATE French_BIN); GO INSERT INSERT INSERT INSERT GO INTO INTO INTO INTO dbo.testSnapshot dbo.testSnapshot dbo.testSnapshot dbo.testSnapshot VALUES VALUES VALUES VALUES (1, (2, (3, (4, 'un'); 'deux'); 'trois'); 'quatre');

-- session 1 BEGIN TRAN; UPDATE dbo.testSnapshot SET texte = 'DEUX' WHERE nombre = 2; -- session 2 SET TRANSACTION ISOLATION LEVEL SNAPSHOT; SELECT * FROM dbo.testSnapshot WHERE nombre = 2;

La deuxime session lit sans problme la ligne. La valeur retourne pour la colonne texte est 'deux', et non 'DEUX', comme ce serait le cas dans une lecture en niveau disolation READ UNCOMMITTED : la valeur lue est la version de la ligne prcdant la modification, par la ligne en cours de modification. Vous pouvez voir les versions de lignes en cours laide des vues de gestion dynamique sys.dm tran active snapshot database transactions et sys.dm_tran_version_store. sys.dm tran active snapshot database transactions donne le spid (session_id) et la dure en secondes depuis la cration de la version (elapsed_time_seconds), sys.dm_tran_version_store vous retourne une ligne par version de ligne, avec lindication de la base de donnes (database_id) source. Attention aux requtes sur sys.dm_tran_version_store : elles peuvent impacter les performances, compte tenu de limportante quantit de rsultats quelles peuvent retourner. Une lecture dans ce niveau gnre aussi une version :
SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRAN; SELECT * FROM dbo.testSnapshot

226

Chapitre 7. Transactions et verrous

WHERE nombre = 2; SELECT session_id, transaction_id, elapsed_time_seconds FROM sys.dm_tran_active_snapshot_database_transactions; ROLLBACK;

Pourquoi cela ? Afin de permettre, dans la mme transaction, dviter les lectures non renouvelables et les lectures fantmes. De fait, ce niveau disolation correspond un niveau SERIALIZABLE, sans le verrouillage. Une requte dans sys.dm_tran_locks ne montre aucun verrou maintenu. Les autres transactions pourront donc modifier la ligne WHERE nombre = 2 loisir. Durant toute la dure de la transaction, notre session en isolation SNAPSHOT verra la version de ligne quivalant la premire lecture. Mais que se passera-t-il si nous cherchons modifier ensuite la ligne dans notre transaction ? Imaginons quaprs avoir excut le SELECT prcdent, une autre session a mis jour la ligne de dbo.testSnapshot, WHERE nombre = 2. Pourrons-nous notre tour effectuer un UPDATE, bas sur une version de ligne prcdant la mise jour externe ? Non. Au moment de lUPDATE dans notre transaction, un conflit sera dtect, et une erreur sera retourne : la cohrence des mises jour est toujours garantie, pour nous protger contre les lost updates. Le mode disolation SNAPSHOT est donc proprement parler un mode de verrouillage optimiste : les donnes lues sont conserves sans verrouillage, avec la supposition quil y a peu de probabilit quun conflit apparaisse.

Compteurs de performances Ces compteurs vous donnent une visibilit sur les statistiques du niveau disolation snapshot : MSSQL:Transactions\Longest Transaction Running Time, MSSQL:Transactions\Snapshot Transactions, MSSQL:Transactions\Update conflict ratio.

Identification du niveau disolation


Vous pourriez avoir envie de savoir dans quel niveau disolation se trouve votre session, ou une autre session en cours dexcution. En SQL Server 2000, la commande DBCC USEROPTIONS tait votre seul salut. Cette commande retourne un jeu de rsultats comportant deux colonnes : [Set Option] et Value. Pour la manipuler en programmation, vous deviez insrer le rsultat dans une table temporaire, en lappelant dans du SQL dynamique. Par exemple :
CREATE TABLE #setoption (so varchar(64), val varchar(64)) INSERT INTO #setoption (so, val) EXEC ('DBCC USEROPTIONS') SELECT val FROM #setoption WHERE so = 'isolation level'

7.4 Attentes

227

Pour connatre le niveau disolation dune autre session que la vtre, vous deviez utiliser une commande DBCC non documente : DBCC PSS, dont la fonction est de produire des informations de bas niveau sur les utilisateurs connects, et les sessions. Depuis 2005, vous avez disposition la vue de gestion dynamique sys.dm_exec_sessions. Par exemple, pour connatre le niveau disolation de votre propre session :
SELECT transaction_isolation_level, CASE transaction_isolation_level WHEN 0 THEN 'Unspecified' WHEN 1 THEN 'ReadUncomitted' WHEN 2 THEN 'Readcomitted' WHEN 3 THEN 'Repeatable' WHEN 4 THEN 'Serializable' WHEN 5 THEN 'Snapshot' END AS isolation_level_description FROM sys.dm_exec_sessions WHERE session_id = @@SPID

Mais lintrt dans le code est limit. Si vous voulez forcer votre niveau disolation, excutez simplement un SET TRANSACTION ISOLATION LEVEL quel que soit votre niveau actuel. Ce sera plus rapide que de tester le niveau auparavant.

7.4 ATTENTES
Comme lisolation de la transaction empche les accs concurrents non compatibles sur les mmes ressources de verrouillage, cela veut dire quune transaction qui veut accder une ressource utilise par une autre transaction doit attendre la libration des verrous non compatibles. Par dfaut, une transaction attend indfiniment la libration de verrous. Vous pouvez limiter cette attente par loption de session LOCK_TIMEOUT :
SET LOCK_TIMEOUT 1000; SELECT @@LOCK_TIMEOUT as lock_timeout;

La valeur est exprime en millisecondes. Le dfaut est -1, cest--dire sans limite. Vous pouvez aussi utiliser lindicateur de table READPAST (voir la section 8.2.1). Les attentes ne sont pas seulement dues au verrouillage mais peuvent tre provoques par les entres/sorties, la mise en attente du processus par lordonnanceur, etc. Nous verrons ensuite les principaux types dattentes.

Une attente peut aussi se produire sur lattribution de mmoire pour la requte. Dans des situations de pression sur la mmoire, cela peut mme provoquer des dpassements de dlai (timeouts).

228

Chapitre 7. Transactions et verrous

Vous obtenez des informations sur les attentes travers les vues de gestion dynamique sys.dm_tran_locks, sys.dm_os_wait_stats, sys.dm_os_waiting_tasks et sys.dm_exec_requests. La DMV sys.dm_os_wait_stats accumule les statistiques sur les attentes (waits). Pour chaque type dattente (lindication de la raison de lattente dun processus SQL Server), cette vue indique principalement le nombre dattentes et leur dure totale en millisecondes. Les valeurs sincrmentent au fur et mesure de lactivit de linstance, et elles sont remises zro comme pour toutes les DMV lors dun redmarrage de celle-ci. Dans ce cas, vous pouvez aussi remettre explicitement les compteurs zro, par exemple avant de lancer un lot de requtes test, laide de cette commande DBCC :
dbcc sqlperf('sys.dm_os_wait_stats', clear)

Voyons en pratique une attente. Excutons ces deux requtes dans deux sessions diffrentes :
-- dans la session 1 BEGIN TRAN UPDATE Sales.Currency SET Name = 'Franc Francais' WHERE CurrencyCode = 'EUR' -- dans la session 2 BEGIN TRAN SELECT * FROM Sales.Currency WITH (TABLOCK) WHERE CurrencyCode = 'CHF'

Que voyons-nous avec une requte sur sys.dm_tran_locks (lgrement modifie pour afficher les deux spid en prsence, vous trouverez cette requte sur le site daccompagnement du livre) ? Rponse dans la figure 7.5.

Figure 7.5 Attentes

Nous voyons que le spid 53 cherche poser un verrou partag sur la table Currency. Le request_status est WAIT, ce qui indique que le verrou est en attente. Nous

7.4 Attentes

229

avons introduit ici, pour lexemple, une petite perversion : la deuxime requte ne serait pas en attente si lindicateur de table WITH (TABLOCK) ne forait un verrou la granularit de table. Comme la transaction du spid 52 a pos un verrou dintention exclusif sur la table Currency, le spid 53 ne peut poser de verrou partag sur cet objet. Au passage, nous voyons aussi, sur la figure 7.5, que deux pages sont verrouilles par le spid 52. Pourquoi deux pages, alors que nous devons modifier une seule ligne ? Vrifions :
DBCC TRACEON (3604) DBCC PAGE (AdventureWorks, 1, 295, 0) DBCC PAGE (AdventureWorks, 1, 2101, 0)

Len-tte de la page 295 nous montre ceci : Metadata: ObjectId = 597577167 Metadata: IndexId = 1, et len-tte de la page 2101 : Metadata: ObjectId = 597577167 Metadata: IndexId = 2. Il sagit bien sr du verrouillage dune page par index : lindex clustered de la table (IndexId = 1), et de lindex nonclustered sur la colonne Name (IndexId = 2), que nous sommes en train de modifier.

Types dattentes
Pour rfrence, nous listons ici les types dattentes que vous rencontrerez le plus souvent, et qui peuvent vous aider dtecter des problmes. Ces attentes sont visibles dans la colonne wait_type des vues de gestion dynamique sys.dm_os_wait_stats et sys.dm_os_waiting_tasks.
Une liste exhaustive est donne dans lexcellent document SQL Server 2005 Waits and Queues , ladresse http://www.microsoft.com/technet/prodtechnol/sql/ bestpractice/performance_tuning_waits_queues.mspx

ASYNC_NETWORK_IO bufferisation dune session TCP, souvent cause dun client qui met du temps traiter lenvoi de SQL Server, ou qui maintient ouvert un jeu de rsultat. Le compteur de performance MSSQL:Wait statistics\Network IO waits est aussi utile pour ce type de diagnostic. CXPACKET change entre des threads parallliss sur la mme requte. Peut indiquer un problme li au paralllisme. Si vous avez beaucoup dattentes de ce type, dsactivez le paralllisme. LCK_... - attentes sur des verrous, voir lencadr pour une surveillance par compteurs de performances. LOGBUFFER et WRITELOG criture et lecture des journaux de transaction. LOGMGR_RESERVE_APPEND attente de troncature pour vrifier si la libration despace dans le journal sera suffisante pour crire lenregistrement actuel. Cest un signe de lenteur de disque, sur des journaux en mode de rcupration simple. PAGEIOLATCH attentes sur des synchronisations entre les pages du buffer et les pages du disque, bon indicateur de contention disque.

230

Chapitre 7. Transactions et verrous

SOS_SCHEDULER_YIELD se produit lorsquune tche sinterrompt volontairement pour redonner sa place, dans le modle non prmptif de lordonnanceur SQLOS. Voir cette entre de blog pour plus de dtails : http:// blogs.msdn.com/sqlcat/archive/2005/09/05/461199.aspx

Surveiller les attentes de verrous Le compteur MSSQL:Wait statistics\Lock waits donne des informations importantes sur les attentes en cours sur le systme. sur les instances : Average wait time (ms) : Temps dattente moyen par type. Cumulative wait time (ms) per second : temps total dattente dans la seconde ; Waits in progress : nombre de processus en cours dattente. Waits started per second : nombre dattentes dmarres par seconde.

7.5 BLOCAGES ET DEADLOCKS


Nous avons prsent les attentes de faon gnrale, et nous avons vu quil y a plusieurs types dattentes. Les attentes spcifiques sur la libration de verrous par une autre transaction, sont appeles des blocages : lattente est involontaire, et dpend dune autre transaction. La transaction en attente dtecte bien entendu cette situation, et elle se considre comme bloque. Cette information de blocage est visible dans les vues de gestion dynamique sys.dm os waiting tasks et sys.dm exec requests (la colonne blocking_session_id y indique le sipd qui bloque la requte en cours). Voici deux exemples de requtes pour dtecter les blocages :
SELECT session_id, wait_duration_ms, wait_type, blocking_session_id, resource_description FROM sys.dm_os_waiting_tasks WHERE blocking_session_id IS NOT NULL; SELECT blocked.session_id, st.text as blocked_sql, blocker.session_id as blocker_session_id, blocker.last_request_end_time FROM sys.dm_exec_requests blocked JOIN sys.dm_exec_sessions blocker ON blocked.blocking_session_id = blocker.session_id CROSS APPLY sys.dm_exec_sql_text(blocked.sql_handle) st;

Bien sr, une session qui est bloque par une premire, peut elle aussi en bloquer une suivante, et ainsi de suite. Lorsque les verrous sont maintenus trop longtemps sur des ressources, il peut se produire ainsi des chanes de blocage qui finissent par paralyser le serveur. Il est donc important de surveiller ces situations.

7.5.1 Dtection des blocages par notification dvnements


Avant SQL Server 2005, le seul moyen pour surveiller et tre averti de blocages survenant sur le serveur, tait de planifier une vrification rgulire de la table systme sysprocesses. Cette approche est toujours possible. Vous pouvez placer

7.5 Blocages et deadlocks

231

dans un travail de lagent SQL planifi pour sexcuter toutes les quelques minutes une requte comme celle-ci (adapte SQL Server 2005-2008) :
SELECT es2.session_id as spid_blocking, es1.session_id as spid_blocked, er1.start_time as blocked_start, er1.row_count, CASE er1.transaction_isolation_level WHEN 0 THEN 'Unspecified' WHEN 1 THEN 'ReadUncommitted' WHEN 2 THEN 'Readcommitted' WHEN 3 THEN 'RepeatableRead' WHEN 4 THEN 'Serializable' WHEN 5 THEN 'Snapshot' END as transaction_isolation_level, DB_NAME(er1.database_id) as db, est1.text as sql_command_blocked, er1.wait_type, es1.host_name as host_name_blocked, es1.program_name as program_name_blocked, es1.login_name as login_name_blocked, es2.host_name as host_name_blocking, es2.program_name as program_name_blocking, es2.login_name as login_name_blocking FROM sys.dm_exec_requests er1 JOIN sys.dm_exec_sessions es1 ON er1.session_id = es1.session_id CROSS APPLY sys.dm_exec_sql_text(er1.sql_handle) est1 JOIN sys.dm_exec_sessions es2 ON er1.blocking_session_id = es2.session_id ORDER BY spid_blocking

qui envoie son rsultat, sil y en a un, par e-mail, laide de Database Mail. Il existe maintenant une mthode intgre qui utilise la notification dvnements (event notification). Cette technologie est base sur les vnements DDL (Data Definition Language) disponibles depuis SQL Server 2005 notamment pour crire des dclencheurs DDL, ainsi que sur Service Broker (voir ci-dessous). Elle permet de rpondre des vnements en mode asynchrone, et denvoyer ces vnements aussi bien sur le serveur SQL local que sur une machine distante. Lvnement que nous allons utiliser sappelle BLOCKED_PROCESS_REPORT. Cet vnement doit tre activ dans les options du serveur pour tre dclench lorsquun processus est bloqu depuis un certain temps.
EXEC sp_configure 'show advanced options', 1 RECONFIGURE GO EXEC sp_configure 'blocked process threshold', 30 RECONFIGURE GO EXEC sp_configure 'show advanced options', 0 RECONFIGURE GO

232

Chapitre 7. Transactions et verrous

La valeur de loption blocked process threshold est exprime en secondes. Elle indique partir de quelle dure de blocage un vnement sera dclench : dans lexemple prcdent, aprs 30 secondes, et chaque 30 secondes suivantes, jusqu ce que le blocage soit libr. En gnral, un blocage en provoque dautres son tour, produisant une chane de processus bloqus les uns par les autres. Lvnement BLOCKED_PROCESS_REPORT ne montre pas cette chane, il se produit pour chaque processus bloqu, individuellement. Vous devez mettre en place la notification. La fonctionnalit devent notification est base sur Service Broker, un systme orient service dchanges de messages intgr SQL Server (un concept parfois nomm SODA : Service Oriented Database Architecture). La description de Service Broker sortant du cadre de cet ouvrage, nous nous contenterons de vous montrer comment mettre pratiquement en uvre une notification dvnement. Vous devez, dans lordre, mettre en place une file dattente Service Broker, un service, ventuellement une route pour indiquer o les messages seront dirigs. Dans lexemple suivant, les noms dobjet (queue, service) peuvent tres modifis, seul le nom du contrat ([http://schemas.microsoft.com/SQL/ Notifications/PostEventNotification]) est fix.
CREATE QUEUE NotifyQueue ; GO CREATE SERVICE NotifyService ON QUEUE NotifyQueue ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]); GO CREATE ROUTE NotifyRoute WITH SERVICE_NAME = 'NotifyService', ADDRESS = 'LOCAL'; GO CREATE EVENT NOTIFICATION BlockedProcessReport ON SERVER WITH fan_in FOR BLOCKED_PROCESS_REPORT TO SERVICE 'NotifyService', 'current database'; GO

Linformation du blocage sera transmise par Service Broker sous forme dun document XML qui sera plac dans la file dattente NotifyQueue. Testons-le en crant une table puis en insrant une ligne dans une session aprs avoir dmarr une transaction explicite. Dans une autre session, essayons de lire la table (dans un niveau disolation au moins quivalant READ COMMITTED) :
USE sandbox GO

7.5 Blocages et deadlocks

233

CREATE TABLE dbo.testblocage ( id int ) BEGIN TRAN INSERT dbo.testblocage ( id ) VALUES( 1 ) GO -- dans une autre session SELECT * FROM sandbox.dbo.testblocage

Le SELECT est mis en attente de libration du verrou exclusif pos par la transaction dinsertion. Au bout du temps dtermin par loption 'blocked process threshold', la notification dvnement posera une ligne dans la file dattente NotifyQueue. Nous pouvons simplement faire un SELECT sur cette queue :
SELECT CAST(message_body as XML) as msg FROM NotifyQueue;

Le rsultat est le suivant :


<EVENT_INSTANCE> <EventType>BLOCKED_PROCESS_REPORT</EventType> <PostTime>2008-05-15T13:16:40.797</PostTime> <SPID>4</SPID> <TextData> <blocked-process-report monitorLoop="22162"> <blocked-process> <process id="process9a8c58" taskpriority="0" logused="0" waitresource="RID: 7:1:204:0" waittime="13843" ownerId="289041" transactionname="SELECT" lasttranstarted="2008-05-15T13:16:26.950" XDES="0x102331d0" lockMode="S" schedulerid="2" kpid="4224" status="suspended" spid="54" sbid="0" ecid="0" priority="0" transcount="0" lastbatchstarted="2008-05-15T13:16:26.950" lastbatchcompleted="2008-05-15T13:11:12.077" lastattention="2008-05-15T13:11:12.077" clientapp="Microsoft SQL Server Management Studio - Query" hostname="BABALUGA-XPS" hostpid="1804" loginname="BABALUGA-XPS\rudi" isolationlevel="read committed (2)" xactid="289041" currentdb="1" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200"> <executionStack> <frame line="1" sqlhandle="0x0200000077178b24288485b9f690b6810f187eb9cd7ba294" /> </executionStack> <inputbuf> SELECT * FROM sandbox.dbo.testblocage </inputbuf> </process> </blocked-process> <blocking-process> <process status="sleeping" spid="53" sbid="0" ecid="0" priority="0" transcount="1" lastbatchstarted="2008-05-15T13:16:19.700" lastbatchcompleted="2008-05-15T13:16:19.700" clientapp="Microsoft SQL Server Management Studio - Query" hostname="BABALUGA-XPS" hostpid="1804" loginname="BABALUGA-XPS\rudi" isolationlevel="read committed (2)" xactid="289008" currentdb="7"

234

Chapitre 7. Transactions et verrous

lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200"> <executionStack /> <inputbuf> CREATE TABLE dbo.testblocage ( id int ) BEGIN TRAN INSERT dbo.testblocage ( id ) VALUES( 1 ) </inputbuf> </process> </blocking-process> </blocked-process-report> </TextData> <DatabaseID>7</DatabaseID> <TransactionID>289041</TransactionID> <Duration>13843000</Duration> <EndTime>2008-05-15T13:16:40.793</EndTime> <ObjectID>0</ObjectID> <IndexID>0</IndexID> <ServerName>BABALUGA-XPS\SQL2005</ServerName> <Mode>3</Mode> <LoginSid>AQ==</LoginSid> <EventSequence>1134</EventSequence> <IsSystem>1</IsSystem> <SessionLoginName /> </EVENT_INSTANCE>

Vous pouvez voir que le rapport inclut les informations des deux sessions, la bloquante (<blocking-process>) et la bloque (<blocked-process>), avec les input buffers (les caches de la dernire instruction de la session) de chacune. La faon correcte de recevoir lvnement est dutiliser linstruction RECEIVE, qui permet de lire une file dattente Service Broker et de la vider des lignes rcupres. RECEIVE peut insrer son tour le rsultat dans une variable de type table seulement. Cest pour cette raison que nous en utilisons une dans le code suivant. Nous plaons lextraction de la queue dans une transaction, afin dannuler la suppression gnre par RECEIVE en cas de problme. Lexemple dmontre aussi lutilisation de XQuery pour lextraction des informations dans la structure XML, laide des mthodes value et query.
CREATE TABLE dbo.BlockedProcesses ( message_body xml NOT NULL, report_time datetime NOT NULL, database_id int NOT NULL, process xml NOT NULL ) GO BEGIN TRY BEGIN TRAN DECLARE @BlockedProcesses TABLE ( message_body xml NOT NULL,

7.5 Blocages et deadlocks

235

report_time datetime NOT NULL, database_id int NOT NULL, process xml NOT NULL ); DECLARE @rowcount int; RECEIVE cast( message_body as xml ) as message_body, cast( message_body as xml ).value( '(/EVENT_INSTANCE/PostTime)[1]', 'datetime' ) as report_time, cast( message_body as xml ).value( '(/EVENT_INSTANCE/DatabaseID)[1]', 'int' ) as database_id, cast( message_body as xml ).query( '/EVENT_INSTANCE/TextData/blockedprocess-report/blocked-process/process' ) as process FROM NotifyQueue INTO @BlockedProcesses; SET @rowcount = @@ROWCOUNT INSERT INTO dbo.BlockedProcesses SELECT * FROM @BlockedProcesses; IF (@rowcount <> @@ROWCOUNT) ROLlBACK ELSE COMMIT TRAN END TRY BEGIN CATCH ROLLBACK TRAN END CATCH

Vous pouvez bien sr utiliser Database Mail (sp_send_dbmail) pour vous notifier.

Comment viter les blocages Les blocages trs courts sont invitables en environnement concurrentiel, ils ne posent en gnral pas de problme srieux. Par contre, nous lavons vu, il faut autant que possible viter les blocages longs. Pour cela, il faut optimiser le code SQL et crer de bons index pour diminuer la dure des transactions. Il faut aussi savoir jouer sur le niveau disolation des transactions. Les niveaux READ UNCOMMITTED et SNAPSHOT (ou READ COMMITTED SNAPSHOT) sont prcieux.

7.5.2 Verrous mortels


Les verrous mortels, ou deadlocks, aussi potiquement appels treintes fatales, ou moins potiquement interblocages, sont des animaux particuliers quon rencontre occasionnellement dans les buissons des applications transactionnelles. Ils ressemblent un peu aux scnarios des westerns qui respectent lunit de lieu, comme Rio Bravo. Dans la dernire partie de ce film dHoward Hawks, les bons, mens par le shrif John T. Chance, dtiennent en otage le frre du chef des mchants, Nathan Burdette. Et les mchants se sont empars de ladjoint du shrif, Dude. Burdette ne librera Dude que si Chance lui rend son frre, et Chance ne librera le frre que si Burdette rend Dude sa libert. Nous savons que dans ce genre de situation, il ny a quune chappatoire : un des deux chefs doit mourir (mauvais exemple ici, car Burdette ne meurt pas).

236

Chapitre 7. Transactions et verrous

SQL Server fonctionne de la mme faon : un deadlock se produit lorsquune transaction attend la libration dun verrou de la part dune autre transaction, qui elle-mme attend la libration dun verrou de la premire transaction. La situation est sans solution, et SQL Server le dtecte laide dun thread de surveillance ddi, qui lance une recherche travers toutes les sessions ouvertes1. Lorsquil rencontre une situation de deadlock, il choisit une victime (la transaction qui a effectu le moins de modification de donnes), et annule (ROLLBACK) cette transaction. Une erreur 1205 est envoye la session choisie comme victime, avec ce message :
Transaction (Process ID XX) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Dans des applications de bases de donnes correctement implmentes, un deadlock devrait tre une situation rare. Elle ne peut se produire que dans le cas dune transaction explicite, dans un contexte particulier. Illustrons-le par un exemple de code :
-- dans la session 1 BEGIN TRAN UPDATE HumanResources.Employee SET MaritalStatus = 'S' WHERE Gender = 'F' UPDATE c SET Title = 'Miss' FROM Person.Contact c JOIN HumanResources.Employee e WITH (READUNCOMMITTED) ON c.ContactID = e.ContactID WHERE e.Gender = 'F' COMMIT TRAN -- dans la session 2 BEGIN TRAN UPDATE c SET Suffix = 'Mrs' FROM Person.Contact c JOIN HumanResources.Employee e WITH (READUNCOMMITTED) ON c.ContactID = e.ContactID WHERE e.Gender = 'F' UPDATE HumanResources.Employee SET MaritalStatus = 'M' WHERE Gender = 'F' COMMIT TRAN

Comme vous le voyez, les deux transactions effectuent des oprations similaires dans un ordre invers. Cest la porte ouverte un deadlock. Il suffit que les transac1. Pour ne pas consommer trop de ressources, le thread de surveillance se lance toutes les cinq secondes, et augmente automatiquement sa frquence si des situations de deadlock sont dtectes. Plus dinformations dans les BOL, entre Detecting and Ending Deadlocks .

7.5 Blocages et deadlocks

237

tions se retrouvent au mme moment entre leurs deux instructions, pour que les instructions suivantes soient fatales. Il est ais, partir de cet exemple, de comprendre comment minimiser le risque de deadlock : rduire autant que possible la dure de ses transactions, et viter deffectuer les mmes oprations dans des ordres diffrents (pour cela, il suffit de centraliser la logique des modifications dans une seule procdure stocke). Malgr tout, une situation de deadlock est toujours possible, plus forte raison si vous devez maintenir des bases de donnes dont vous navez pas crit les procdures. Il est donc prudent de mettre en place une dtection de deadlocks. Cest ais en crant une alerte de lagent SQL qui vous notifie au dclenchement de lerreur 1205. Si vous commencez rencontrer des deadlocks, vous avez quelques outils qui vous permettent de remonter la source du problme.

Analyse des verrous mortels


Les drapeaux de trace 1204 et 1222 activent un mode de rapport dtaill de deadlock. Ils peuvent tre utiliss sparment ou ensemble, pour afficher les informations de deux faons diffrentes. Il nest possible de les activer que globalement1, soit en les ajoutant en paramtres de lancement de SQL Server : -T1204 ou -T1222, dans le gestionnaire de configuration (voir figure 7.6)

Figure 7.6 Paramtres de dmarrage 1. En SQL Server 2000 ces flags taient activables par session, ce nest plus le cas en SQL Server 2005.

238

Chapitre 7. Transactions et verrous

soit laide de DBCC TRACEON, en indiquant -1 en second paramtre, ce qui indique lactivation dun drapeau global :
DBCC TRACEON(1204, -1)

Aprs activation, le moniteur de deadlocks inscrira des informations sur la chane de deadlocks dans le journal derreurs de SQL Server (le fichier ERRORLOG, situ dans le sous-rpertoire LOG du rpertoire o SQL Server stocke ses donnes). Voici le rsultat dune journalisation ralise aprs activation du drapeau 1204 :
Deadlock encountered .... Printing deadlock information Wait-for graph Node:1 KEY: 5:72057594044153856 (07005a186c43) CleanCnt:3 Mode:X Flags: 0x0 Grant List 0: Owner:0x1016D080 Mode: X Flg:0x0 Ref:0 Life:02000000 SPID:52 ECID:0 XactLockInfo: 0x11EBAF64 SPID: 52 ECID: 0 Statement Type: UPDATE Line #: 1 Input Buf: Language Event: UPDATE c SET Title = 'Miss' FROM Person.Contact c JOIN HumanResources.Employee e WITH (READUNCOMMITTED) ON c.ContactID = e.ContactID WHERE e.Gender = 'F' Requested By: ResType:LockOwner Stype:'OR'Xdes:0x10687A80 Mode: U SPID:51 BatchID:0 ECID:0 TaskProxy:(0x1090A378) Value:0x1016b6c0 Cost:(0/19956) Node:2 KEY: 5:72057594043236352 (2e0032505f99) CleanCnt:2 Mode:X Flags: 0x0 Grant List 0: Owner:0x1016B420 Mode: X Flg:0x0 Ref:0 Life:02000000 SPID:51 ECID:0 XactLockInfo: 0x10687AA4 SPID: 51 ECID: 0 Statement Type: UPDATE Line #: 1 Input Buf: Language Event: UPDATE HumanResources.Employee SET MaritalStatus = 'M' WHERE Gender = 'F' Requested By: ResType:LockOwner Stype:'OR'Xdes:0x11EBAF40 Mode: U SPID:52 BatchID:0 ECID:0 TaskProxy:(0x11BEA378) Value:0x1016b480 Cost:(0/13864) Victim Resource Owner: ResType:LockOwner Stype:'OR'Xdes:0x11EBAF40 Mode: U SPID:52 BatchID:0 ECID:0 TaskProxy:(0x11BEA378) Value:0x1016b480 Cost:(0/13864)

Comme vous le voyez, vous avez les informations de base ncessaire comprendre la source du deadlock : les spids, et la dernire commande excute dans chaque spid (linputbuffer). La section Victim Resource Owner indique qui a t choisi

7.5 Blocages et deadlocks

239

comme victime. Le drapeau 1222 affiche les informations dans un ordre diffrent, et donne plus dinformations sur les deux sessions concernes par le deadlock. Vous pouvez galement tracer les deadlocks avec le profiler, et obtenir une vue graphique de la chane. Les vnements Lock:Deadlock, Lock:Deadlock Chain et Lock:Deadlock Graph vous indiquent lapparition dune deadlock, la chane de rsolution, et un dtail en format XML. Vous trouverez en figure 7.7 les vnements lors dun deadlock.

Figure 7.7 Deadlock graph

Vous voyez que la chane de deadlock et le graph, sont des vnements dclenchs par le systme, sur des SPID infrieurs 50 (donc des spids systme). La colonne EventSequence permet de trier correctement la squence dvnements. Un certain nombre de colonnes ajoutent des informations. Nous voyons ici IntegerData, qui retourne un numro de deadlock SQL Server leur attribue un numro incrmentiel et Type indique sur quelle ressource de verrouillage il sest produit. Lvnement Deadlock graph saffiche graphiquement dans le profiler. Vous voyez les ressources en jeu, les types de verrous poss, sur quelques objets. La victime est marque dune croix. En laissant votre souris sur un des processus, vous obtenez linputbuffer en tooltip. Maintenant que vous avez tous les lments en main, vous pouvez dire vos deadlock, comme John T. Chance : Sorry dont get it done, Dude. Thats the second time you hit me. Dont ever do it again .

8
Optimisation du code SQL

Objectif
Les efforts doptimisation doivent dabord se porter sur le code SQL et la structure des donnes, avant de considrer des mises jour matrielles. Cest l o les problmes se posent en gnral, et cest l o leffet de levier est le plus important. Ce nest que lorsquon a la garantie que le code et le schma sont optimiss que la mise jour matrielle doit tre envisage. Dans ce chapitre, nous allons vous prsenter les plans dexcutions gnrs par le moteur doptimisation de SQL Server, qui vont vous permettre de juger de la qualit de votre code SQL. Nous parlerons ensuite de diffrentes faons dcrire du code plus performant.

8.1 LECTURE DUN PLAN DEXCUTION


Lorsquune requte est reue par le moteur relationnel, celui-ci vrifie dabord si son plan dexcution est prsent dans le cache de plans. Si non, il doit crer ce plan. Cette phase est segmente en quatre tapes. Nous avons schmatis lexcution dune requte dans la figure 8.1. Le moteur de requte parse (value) dabord la requte pour en vrifier la syntaxe, et la rcrit dans une structure qui sera utilise pour loptimisation. La phase de normalisation consiste vrifier lexistence des objets rfrencs, et la logique de la requte par rapport ces objets (SELECT * FROM ProcdureStocke, par exemple, est illogique).

242

Chapitre 8. Optimisation du code SQL

Figure 8.1 Schma de compilation

La compilation consiste btir un arbre de squence (sequence tree) pour dcomposer la requte. cette tape des conversions implicites de types de donnes sont appliques si ncessaire, ainsi que le remplacement de lappel aux vues, par lappel aux tables sous-jacentes. Un graphe de requte (query graph) est cr, qui est ce qui est envoy loptimiseur. Loptimisation intervient pour les requtes DML (SELECT, INSERT, UPDATE et DELETE). Loptimiseur reoit un graphe de requte, et essaie de trouver la meilleure stratgie dexcution. Loptimiseur SQL Server est fond sur le cot (cost-based optimizer), cest--dire quil va orienter son choix vers le plan le moins coteux. Pour cela, il teste dabord la possibilit dutiliser un plan lger (trivial plan). Dans le cas par exemple dun simple INSERT VALUES , inutile de chercher bien loin, la manire de raliser cette commande est vidente. Si la requte ne peut tre satisfaite par un plan lger le moteur doptimisation va btir en parallle des plans dexcution, utilisant sa connaissance de la structure des objets (prsence dindex, de contraintes CHECK, partitions, statistiques de distribution). Il attribue un cot chaque plan, un cot de temps de dexcution et de consommation dentres/sorties valu de faon heuristique. Pour ne pas prolonger cette phase elle-mme coteuse, il retient le plan ayant un cot raisonnable. Ainsi, le plan choisi nest pas forcment le meilleur dans labsolu, mais le premier trouv suffisamment efficace pour rsoudre la requte.

8.1 Lecture dun plan dexcution

243

Le plan ainsi gnr est mis en cache, aprs avoir t potentiellement paramtr en cas dauto-paramtrage. Ce plan dexcution est linformation essentielle dont nous avons besoin pour valuer la qualit du code de notre requte et ses performances estimes, et pour considrer la cration dindex utiles. Nous avons plusieurs moyens de le visualiser. Le plus simple est dafficher une reprsentation graphique de ce plan dans SSMS. Deux versions de ce plan y sont disponibles, le plan dit estim, et le plan dit rel. Le plan estim est celui qui est produit par loptimiseur, et qui est affich avant lexcution de la requte. Le plan dexcution rel est le mme plan exactement que le plan estim, puisque ce plan est utilis sans aucune sorte de modification dynamique en cours dexcution de la requte. Simplement, le plan rel inclut quelques statistiques dexcutions effectives, qui peuvent diffrer des statistiques estimes, principalement sur le nombre de lignes affectes. La combinaison de touches CTRL+L affiche le plan estim des instructions de la fentre de requte active, ou des instructions slectionnes. CTRL+M prpare laffichage du plan rel, dans un onglet supplmentaire de la fentre de rsultat. Vous pouvez aussi les activer par des boutons de la barre doutils de lditeur SQL, indiqus sur la figure 8.2.

Figure 8.2 Barre doutils

Le plan dexcution est ainsi affich dans une reprsentation graphique, qui est ladaptation synoptique dun plan gnr en format XML. Ce plan peut tre sauvegard dans un fichier, avec un clic droit sur le plan, et la commande Save execution plan as . Lextension par dfaut est .sqlplan. Lorsque vous ouvrez un fichier portant cette extension avec SSMS, la reprsentation graphique est gnre, ce qui est pratique pour afficher des plans exports dune vision XML, ou partir dune trace. Nous trouvons, en figure 8.3, un plan graphique simple.

244

Chapitre 8. Optimisation du code SQL

Figure 8.3 Plan dexcution graphique

correspondant la requte :
SELECT FirstName, LastName FROM Person.Contact WHERE LastName = 'Abercrombie';

Vous voyez au passage que la requte a t auto-paramtre par le moteur relationnel, puisque le texte (statement) visible dans le plan dexcution est :
SELECT [FirstName], [LastName] FROM [Person].[Contact] WHERE [LastName] =@1;

Nous verrons ce quest lauto-paramtrage dans la section 9.3.

Chaque icne reprsente un oprateur du plan, cest--dire une opration effectue sur les donnes. Nous les dtaillerons dans la section suivante. Le plan se lit de droite gauche. La premire opration effectue se trouve en haut et droite. Chaque oprateur produit une table en mmoire dont le rsultat qui est envoy loprateur suivant, ce quexpriment les flches. Le nombre de lignes transportes est reprsent visuellement par lpaisseur des flches. Sur la figure 8.3, toutes les flches sont fines, elles ne conduisent donc quun petit nombre de lignes. Chaque oprateur ainsi que linstruction entire affichent un cot relatif en pourcentage. Lors de laffichage du plan de plusieurs instructions, le cot par instruction montre une estimation du poids relatif de chacune, ce qui est pratique pour avoir un aperu des performances compares de plusieurs syntaxes de requte. La touche F4 permet dafficher les proprits du plan ou de chaque oprateur, selon la slection effectue la souris. De mme, en laissant le pointeur de la souris pos sur un oprateur ou une flche, on peut en voir les dtails dans une fentre de type info-bulle. Nous voyons en figure 8.4 laffichage des proprits et de linfo-bulle.

8.1 Lecture dun plan dexcution

245

Figure 8.4 Proprits et info-bulle.

Sont notamment utiles lestimation du nombre de lignes (produit par lanalyse des statistiques de distribution sur la colonne), et la taille moyenne estime de la ligne en octets. La taille multiplie par le nombre nous donne une estimation du volume de donnes que le moteur de stockage va devoir transmettre doprateur oprateur (nous pouvons voir directement ce calcul dans linfo-bulle des flches). Le cot estim de loprateur est une addition des cots CPU et I/0 estims. Vous voyez galement sur quel objet est effectue lopration (sur la figure 8.4, cest une recherche dans lindex nix$Person_Contact$LastName), quelles sont les colonnes rsultantes qui sont envoyes loprateur suivant (output list), et le prdicat de filtre (le scalaire N'Abercrombie', qui est converti automatiquement en nvarchar pour correspondre au type de donnes de la colonne LastName). Le cot du sous-arbre (subtree cost) est simplement la somme du cot de tous les oprateurs prcdents plus le courant, indiquant donc ce qua dj cot le trajet.

Si vous avez dsactiv la cration ou la mise jour automatique de statistiques sur votre base de donnes, vous verrez apparatre un signe dalerte sur les oprateurs de votre plan qui nauront pas les statistiques ncessaires lestimation dune cardinalit. Dans le plan en XML, linformation sera contenue dans un lment <Warnings><ColumnsWithNoStatistics>.

246

Chapitre 8. Optimisation du code SQL

Il peut exister au plus deux versions dun mme plan en cache : une version monoprocesseur et une version paralllise. SQL Server choisit dutiliser lun ou lautre selon la charge des CPU lexcution. Si le plan paralllis est choisi, tous les oprateurs enrls dans lexcution parallle seront agrments dune image lindiquant, comme en figure 8.5.

Figure 8.5 Plan dexcution paralllis

Pour ce plan, nous avons forc un mauvais plan en ajoutant un indicateur dindex mal propos. Lexcution en parallle doit joindre les rsultats provenant des diffrents threads, ce quil fait ici dans loprateur Parallelism (Gather Streams) . Grce cet affichage graphique, vous avez tous les lments pour comprendre rapidement comment votre requte est excute. Afin de loptimiser, concentrezvous sur les oprateurs les plus coteux, et les flches les plus paisses. Un des objectifs de loptimisation est notamment de filtrer le nombre de lignes traiter au plus tt dans larbre du plan dexcution, le pire tant reprsent par un nombre trs important de lignes traites au dbut, pour aboutir un petit nombre en fin de plan. Visuellement, les flches sont trs paisses droite, puis trs fines gauche. Par exemple, que constatons-nous sur la figure 8.5 ? Comme nous avons forc lutilisation dun index inutile pour rsoudre le filtre de la clause WHERE, SQL Server est oblig de nous obir : il parcourt le nud feuille de lindex (index scan) pour en extraire les ContactID contenus dans la cl de lindex (tous les index contiennent en plus la cl de lindex clustered). Le rsultat, 19 973 lignes sont retournes, pour un total de 125 Ko, comme nous pouvons le voir sur la figure 8.6.

Figure 8.6 Envoi dun oprateur lautre

8.1 Lecture dun plan dexcution

247

Ces lignes donnant le ContactID, il faut maintenant aller chercher les colonnes FirstName et LastName dans la table. Pour cela, une recherche travers lindex clustered doit tre faite pour chaque ContactID, ce qui est reprsent ici par loprateur de boucle imbrique (nested loop, dont nous parlerons dans la section 8.1.2 sur les algorithmes de jointure). Pour chaque ligne de la flche du haut, une recherche est effectue avec loprateur du bas, ici un bookmark lookup de type Key lookup : une recherche (seek) dans lindex clustered. Cest cette opration qui plombe toute la requte, reprsentant 98 % de son cot total. En effet, il faut parcourir 19 973 fois lindex clustered. La flche qui la relie au nested loop le montre (dans le plan estim, cette flche est fine, parce que lestimation qui est faite par SQL Server est de une seule ligne retourne). Dans les proprits de loprateur Nested loop (ou de la flche qui vient du Key lookup), nous avons linformation du nombre de lignes donc de seeks de lindex clustered qui ont t traites par thread, comme nous le voyons sur la figure 8.7.

Figure 8.7 Nested loop sur plusieurs threads

Lorsque toutes les lignes sont retournes avec la valeur de FirstName et LastName, elles sont envoyes (1,4 Mo passe du nested loop au filter) un oprateur de filtre, qui, travaillant en mmoire, va liminer les lignes qui ne correspondent pas au prdicat de recherche LastName = N'Abercrombie'. Il nen restera que trois lignes, envoyes la synchronisation des threads parallliss, puis au client comme jeu de rsultats. Nous verrons un peu plus loin les oprateurs plus en dtail.

Rcupration des plans dexcution


Outre laffichage graphique intgr SSMS, vous avez plusieurs moyens de gnrer des plans dexcution. Tout dabord, un certain nombre de commandes de session permettent laffichage du plan : SET SHOWPLAN_XML ON : affiche le plan dexcution estim complet en format XML, sans excuter linstruction. SET SHOWPLAN_TEXT ON : affiche le plan dexcution estim simplifi en format texte (une ligne de jeu de rsultat par oprateur du plan), sans excuter linstruction.

248

Chapitre 8. Optimisation du code SQL

SET SHOWPLAN_ALL ON : affiche le plan dexcution estim dtaill (avec estimations et alertes) en format texte (une ligne de jeu de rsultat par oprateur du plan), sans excuter linstruction. SET STATISTICS XML ON : affiche le plan dexcution complet rel en format XML, aprs excution de linstruction. SET STATISTICS PROFILE ON : affiche le plan dexcution dtaill rel en format texte (une ligne de jeu de rsultat par oprateur du plan), aprs excution de linstruction. Les commandes SET SHOWPLAN... doivent tre lances dans leur propre batch, sans aucune autre instruction. Vous devez donc les sparer de vos requtes par un GO dans SSMS.

Les commandes non XML sont considres par Microsoft comme en voie dobsolescence, elles seront peut-tre supprimes dans une version ultrieure de SQL Server. SQL Server 2008 les supporte toujours.

Les commandes SET SHOWPLAN... affichent donc le plan estim, et les commandes SET STATISTICS le plan rel. Les quelques informations supplmentaires du plan rel sont visibles dans le plan XML dans les lments <RunTimeInformation> qui y sont ajouts. Sur laffichage graphique du plan, vous les trouvez dans les affichages dtaills lorsque vous glissez votre souris sur un oprateur, ils commencent par Actual... . Vous pouvez aussi rcuprer ces plans partir dune trace SQL. Les vnements slectionner sont dans le groupe dvnements Performance : Lvnement Performance statistics se dclenche quand un plan dexcution est insr dans le cache de plans, quand il est recompil, ou quand il est vid du cache. La colonne EventSubClass contient lidentifiant du type dvnement, qui peut tre un des suivants : 0 un nouveau batch nest pas encore dans le cache. La colonne TextData contient le code SQL de la requte. 1 des instructions dans une procdure stocke ont t compiles. 2 des instructions dans un batch de requtes ad hoc ont t compiles. 3 une requte a t supprime du cache et les donnes historiques de performances vont tre dtruites. 4 une procdure stocke a t supprime du cache et les donnes historiques de performances vont tre dtruites. 5 un dclencheur a t supprim du cache et les donnes historiques de performances vont tre dtruites. Les types 4 et 5 ne sont disponibles quen SQL Server 2008.

8.1 Lecture dun plan dexcution

249

Les vnements 1, 4 et 5 retournent les colonnes DatabaseID et ObjectID, le nom de lobjet (procdure ou dclencheur) peut tre retrouv grce la fonction OBJECT_NAME() qui accepte en deuxime paramtre un DB_ID depuis le service pack 2 de SQL Server 2005.

Dans les vnements 1 et 2, la colonne BinaryData contient le plan dexcution gnr en format binaire, de mme les colonnes CPU et Duration donnent les temps de compilation. La colonne IntegerData donne la taille en kilo-octets, du plan gnr. Les colonnes BigintData1 et BigintData2 donnent respectivement le nombre de fois o ce plan a t recompil, et la taille mmoire qui a t ncessaire la compilation, en Ko. Tous retournent les colonnes SqlHandle et PlanHandle (sauf lvnement 0 qui ne retourne que le SqlHandle) qui donnent des identifiants utiles pour obtenir plus dinformations partir des vues de gestion dynamique que nous prsenterons dans la section suivante. Les vnements Showplan XML, Showplan XML For Query Compile et Showplan XML Statistics Profile affichent des plans en XML dans la colonne TextData : Showplan XML : retourne le plan dexcution qui est utilis par la requte, sans les statistiques dexcution. Cela correspond au plan estim. Lvnement se dclenche aprs lvnement SQL ou SP Starting, mais avant Showplan XML Statistics Profile. Showplan XML For Query Compile : retourne le plan dexcution tel quil est compil et mis en cache. Cet vnement se dclenche durant la phase de compilation, avant excution (donc avant un vnement Starting), et avant la mise en cache (vnement SP:CacheInsert). Showplan XML Statistics Profile : retourne le plan dexcution utilis par la requte avec les statistiques dexcution. Cela correspond au plan rel. Lvnement se dclenche juste avant lvnement SQL ou SP Completed. Dans les trois cas, BinaryData contient le cot estim de la requte, et IntegerData le nombre estim de lignes retournes ; ObjectID et ObjectName contiennent lID et le nom de lobjet, ObjectType le type dobjet.

Autres vnements Les vnements Showplan Text, Showplan Text (Unencoded), Showplan All, Showplan All For Query Compile et Showplan Statistics Profile existent pour compatibilit. Vous pouvez les utiliser pour voir les plans en format texte. Les vnements produisant des plans en XML sont plus complets et devraient tre prfrs.

250

Chapitre 8. Optimisation du code SQL

Le profiler affiche le plan dexcution graphique, comme SSMS, sur les vnements qui retournent ce plan en XML, ou en format binaire, comme nous le voyons sur la figure 8.8.

Figure 8.8 Plan dexcution dans le profiler

Un dernier mot : la gnration en masse de plans dexcution dans une trace est non seulement inutile et provoque un raz-de-mare dinformations, mais est aussi trs pnalisante pour les performances. Tracez ces vnements seulement au besoin, en filtrant votre trace.

Vues de gestion dynamique


Vous trouvez le code SQL de vos requtes, ainsi que leur plan dexcution, dans certaines vues de gestion dynamique, celles qui lisent le contenu du cache de plans, et celles qui affichent les requtes excutes dans le SQL Server, en temps rel. Deux fonctions permettent de retourner respectivement le code SQL de linstruction (sys.dm_exec_sql_text) et le plan dexcution XML (dm_exec_query_plan). Exemple dextraction des textes et des plans des tches en cours :
SELECT er.session_id, er.start_time, er.status, er.command, st.text, qp.query_plan FROM sys.dm_exec_requests er CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) st CROSS APPLY sys.dm_exec_query_plan(er.plan_handle) qp

La colonne session_id reprsente les SPID, vous pouvez donc liminer votre SPID avec un WHERE session_id <> @@SPID, pour viter de voir cette requte que vous venez de lancer, dans la liste.

8.1 Lecture dun plan dexcution

251

Le plan dexcution (colonne query_plan) tant de type XML, vous pouvez y appliquer du XQuery. Exemple dextraction du code SQL de lintrieur du plan :
WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan') SELECT er.session_id, er.start_time, er.status, er.command, qp.query_plan.value('(/ShowPlanXML/BatchSequence/Batch/Statements/ StmtSimple/@StatementText)[1]', 'varchar(8000)') as sql_text FROM sys.dm_exec_requests er CROSS APPLY sys.dm_exec_query_plan(er.plan_handle) qp

(la ligne de requte XQuery est coupe dans lexemple).


Ce qui a permis Bob Beauchemin de crer une intressante procdure de recherche doprateur dans un plan, que nous reproduisons ici. Vous pouvez la trouver dans cette entre de blog : http://www.sqlskills.com/blogs/bobb/2006/03/ 03/MoveOverDevelopersSQLServerXQueryIsActuallyADBATool.aspx.
CREATE PROCEDURE LookForPhysicalOps (@op VARCHAR(30)) AS SELECT sql.text, qs.EXECUTION_COUNT, qs.*, p.* FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(sql_handle) sql CROSS APPLY sys.dm_exec_query_plan(plan_handle) p WHERE query_plan.exist(' declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/ 07/showplan"; /ShowPlanXML/BatchSequence/Batch/Statements//RelOp/ @PhysicalOp[. = sql:variable("@op")] ') = 1 GO EXECUTE LookForPhysicalOps 'Clustered Index Scan' EXECUTE LookForPhysicalOps 'Hash Match' EXECUTE LookForPhysicalOps 'Table Scan'

8.1.1 Principaux oprateurs


Les oprateurs de plan sont spars en deux niveaux. Loprateur logique reprsente laction logique entreprise, au niveau de lalgbre relationnelle (par exemple, Full Outer Join), ou du concept de recherche, tandis que loprateur physique est la mthode algorithmique utilise pour implmenter cette opration (par exemple, nested loop, ou boucle imbrique). Dans notre exemple, il sagit dune recherche dindex (index seek), un oprateur la fois logique et physique. Les premiers oprateurs prsents sont en gnral une extraction de donnes (ou un calcul sur un scalaire). Vous trouvez la description des oprateurs et leurs icnes dans les BOL, sous Graphical Execution Plan Icons (SQL Server Management Studio) , nous listons ici les plus courants et les plus significatifs.

252

Chapitre 8. Optimisation du code SQL

Bookmark Lookup recherche de lignes dans une table aprs parcours ou recherche dans un index nonclustered, laide dune cl dindex clustered ou dun RowID. Le bookmark lookup est le plus souvent exprim dans le plan soit par un oprateur Key Lookup, soit par un oprateur RID Lookup. Clustered Index Scan parcours du nud feuille dun index clustered, donc de la table elle-mme. Clustered Index Seek recherche dans un index clustered. Cette recherche nest jamais suivie dun bookmark lookup, puisque la table fait partie de lindex clustered. Split et Collapse ces oprateurs se rencontrent dans certaines mises jour (UPDATE) o une contrainte dunicit doit tre vrifie. Un UPDATE seul sur une colonne avec contrainte unique pourrait dboucher sur linsertion de doublons, parce que la mise jour est excute ligne par ligne par le moteur de stockage. Il faut donc vrifier la totalit des lignes mises jour en une fois. Cela se fait par la squence doprateurs reproduite en figure 8.9.

Figure 8.9 Split et collapse

Le split est la sparation dun UPDATE en un DELETE suivi dun INSERT. Des oprateurs de filtre puis de tri organisent les lignes supprimes puis insres dans lordre de la cl unique, afin de vrifier quil ny a pas deux insertions qui portent la mme valeur, le collapse runit ensuite les lignes sur la cl unique (vous trouvez une proprit GROUP BY qui indique sur quelles colonnes le collapse est effectu)1. Compute Scalar calcul dune valeur scalaire partir dune expression, et de tout type de source. Par exemple un COUNT(*) partir dun scan dindex est implment en tant que Stream Aggregate, puis Compute Scalar. Un Compute Scalar est visible partout o une colonne doit contenir une valeur calcule, concatne, etc. Concatenation copie des lignes dun jeu de rsultats vers un autre, typiquement utilis pour un UNION ou UNION ALL. Dans le cas dun UNION, si les entres sont tries, un Merge Join sera souvent prfr, pour permettre un ddoublonnage plus rapide. Constant Scan introduit des valeurs constantes comme nouvelles lignes. Un Compute Scalar est souvent utilis ensuite pour ajouter des colonnes cette ligne. Vous le verrez par exemple lorsque vous voudrez gnrer un jeu de rsultats vide (par exemple pour crer une structure de table vide laide de SELECT INTO). Des requtes telles que celles-ci :
1. http://blogs.msdn.com/craigfr/archive/2007/09/06/maintaining-unique-indexes.aspx

8.1 Lecture dun plan dexcution

253

SELECT TOP 0 * FROM Person.Address; SELECT * FROM Person.Address WHERE 1 = 0;

crent un plan trs simple, composs dun seul Constant Scan. Eager Spool et Lazy Spool les oprateurs de spool crent une table de travail dans tempdb. Leur diffrence vient de leur enthousiasme le faire. Le spool enthousiaste (eager) insre toutes les lignes en une seule fois dans la table de travail, alors que le spool paresseux (lazy) le fait une ligne aprs lautre : lorsque loprateur dpendant du Lazy Spool lui demande une ligne, celui-ci en fait la demande son oprateur enfant, et stocke la ligne dans le spool. Filter filtre un jeu de lignes selon le critre prsent dans sa proprit Argument. Hash Aggregate calcul dagrgation utilis plutt sur des volumes importants, et uniquement quand la requte utilise un GROUP BY. Une table de hachage est cre pour permettre un calcul sur tous les sous-ensembles en mme temps. Cette mthode utilise plus de mmoire et verrouille plus que le Stream Aggregate. Hash Match test de prsence de ligne sur une table de hachage btie partir de lentre de loprateur enfant (la liste des colonnes faisant partie du hachage est dans le prdicat HASH:() de la proprit Argument). Les valeurs de hachage sont calcules pour chaque ligne teste et compares la table de hachage, pour tester leur prsence. Cest un oprateur physique qui dpend de trois types doprateurs logiques : join loprateur enfant du haut est celui sur lequel la table de hachage est base, loprateur du bas est celui partir duquel chaque ligne est teste ; aggregate ou distinct la table de hachage est cre partir de lentre, les doublons sont supprims, ou les agrgations calcules, puis la comparaison est faite avec un scan de la mme entre pour conserver les match ; union la table de hachage est calcule partir de loprateur enfant du haut. Les doublons sont limins. Les lignes de lentre du bas sont testes, et les non-match sont retourns. Key Lookup recherche de lignes dans une table clustered, partir dune cl dindex clustered prsente dans un index nonclustered, aprs parcours ou recherche de cet index. Cest une de deux formes particulires du bookmark lookup. Merge Interval normalise plusieurs intervalles pour en produire un seul, qui sera utilis dans une recherche dindex. Nonclustered Index Spool un index nonclustered est cr sur une table de travail dans tempdb, pour amliorer les performances de recherche dans cette table. Les oprateurs Remote (Delete, Insert, Query, Scan, Update) indiquent des oprations sur un serveur distant ou un serveur li (le plus souvent travers un serveur li, linked server, les serveurs distants, remote server, tant obsoltes). RID Lookup recherche de lignes dans une table heap, partir dun identifiant de ligne (Row ID) prsent au niveau feuille dun index nonclustered, aprs parcours

254

Chapitre 8. Optimisation du code SQL

ou recherche de cet index. Cest une de deux formes particulires du bookmark lookup ; Vous verrez souvent des oprateurs de Segment, Sequence et SequenceProject dans les requtes impliquant des fonctions de fentrage (une syntaxe de la norme SQL implmente en SQL Server depuis la version 2005). Elles servent calculer le rsultat de ces fonctions lintrieur du jeu de rsultats. SQL Server 2008 amliore les performances des fonctions de fentrages. Sort tri des lignes dentre. La proprit ORDER BY indique la ou les colonnes sur lesquelles sopre le tri. Spool et Table Spool Spool sauve le rsultat intermdiaire dans une table de travail dans tempdb, Table Spool fait de mme partir des lignes parcourues et slectionnes dans loprateur enfant. Stream Aggregate calcul dagrgation sur un ensemble ou sous-ensemble provenant dune source trie. Si la source nest pas dj trie (elle lest par exemple si elle provient dun scan dindex, puisque la cl de lindex est trie), un oprateur Sort devra prcder celui-ci (ou un Hash Aggregate sera utilis, selon la taille de la table). Vous trouvez dans les proprits de loprateur lexpression de calcul1. Table Scan scan de table sans index clustered, donc de table heap. Table-valued Function une fonction utilisateur retournant une table est appele. La table retourne est stocke dans tempdb (cest une variable de type table), elle sera traite dans tempdb. UDX une opration XML, comme un FOR XML ou une requte XQuery.

8.1.2 Algorithmes de jointure


SQL Server implmente trois algorithmes physiques pour effectuer des jointures. Nous allons les prsenter. La boucle imbrique (nested loop) est lalgorithme de base, qui est le plus simple et le plus utilis dans les plans dexcution. Il consiste extraire des lignes dune premire table (la table externe), et rechercher leur correspondance dans une seconde table (la table interne), comme si on cherchait une liste de rfrences dans un premier livre, pour aller chercher chaque rfrence dans un second. Lalgorithme peut se schmatiser ainsi :
pour chaque ligne de la table externe pour chaque ligne de la table externe joindre les lignes retour des lignes

1. http://blogs.msdn.com/craigfr/archive/2006/09/13/752728.aspx

8.1 Lecture dun plan dexcution

255

Do le nom de boucles imbriques. Cet algorithme fonctionne trs bien pour les petits volumes, mais son cot augmente proportionnellement aux nombres de lignes traiter. Les bookmark lookups sont exprims en boucles imbriques dans les plans depuis SQL Server 2005. La fusion (merge join) consiste prendre deux tables tries selon les mmes colonnes, et extraire les correspondances entre la premire table et la seconde. Comme elles sont tries, le parcours se fait toujours vers lavant, chaque correspondance est conserve, et les lignes suivantes sont testes. Cest donc un algorithme trs rapide, mais il implique un tri pralable, et une clause dquijointure (on ne peut comparer de cette faon que des galits). Le tri tant trs coteux, la jointure de fusion est donc utilise principalement sur les tables ordonnes par un index clustered, ou sur les nuds feuilles dindex. Le hachage (hash join) est privilgi sur les jeux de rsultats importants, car il permet, aprs prparation, de raliser de grosses jointures relativement rapidement. Il fonctionne en deux phases : il cre premirement une table de hachage sur les colonnes de la condition de recherche sur la premire table. Ensuite, il parcourt la deuxime table, cre un hachage pour chaque condition de recherche et effectue la comparaison avec la table de hachage. Cet algorithme ne fonctionne quavec des clauses dquijointures. Il souffre de quelques dsavantages : il est bloquant , car la premire tape doit tre compltement termine avant de passer la seconde (alors que le nested loop et le merge peuvent travailler au fil de leau), et il est trs consommateur de mmoire. SQLOS lui rserve une quantit de mmoire estime pour son travail, mais si cette estimation tait trop optimiste, il doit, pendant lexcution, baver sur tempdb (on parle de spilling). Cela ralentit bien sr nettement lopration. Le hachage est utilis aussi pour les calculs dagrgations, et ce dbordement peut aussi se produire dans ce cas.

Dbordements de hachages
Vous avez un vnement de trace qui vous permet de dtecter les dbordements dans tempdb dus des hachages (Hash Aggregate et Hash Join) : Errors and Warnings : Hash Warning. Vous pouvez utiliser des indicateurs de requte pour forcer un algorithme de calcul dagrgation : option(order group) force le Stream Aggregate ; option(hash group) force le Hash Aggregate. Attention loption hash group : un Hash Aggregate nest possible quen prsence dun GROUP BY. Si vous utilisez cet indicateur dans une agrgation scalaire (un calcul dagrgat sans regroupements, donc sans clause GROUP BY), une erreur vous sera renvoye la compilation : Query processor could not produce a query plan because of the hints defined in this query... .1

256

Chapitre 8. Optimisation du code SQL

8.2 GESTION AVANCE DES PLANS DEXCUTION


Vous pouvez modifier le plan dexcution gnr par loptimiseur de deux faons : soit en ajoutant la requte des indicateurs spcifiques forant certaines stratgies logiques ou physiques de rsolution de la requte, soit en y appliquant un plan dexcution fait main complet, laide des guides de plan (plan guides). Nous allons passer les deux solutions en revue. Mais avant cela, il nous faut rpter encore que ces fonctionnalits ne sont considrer quen dernier ressort, et lorsque vous savez ce que vous faites. Soyez notamment conscient des rpercussions lors de changement de structure ou de distribution des valeurs dans les tables. Par exemple, un guide de plan qui force lutilisation dun index qui a disparu, gnre une erreur.

8.2.1 Indicateurs de requte et de table


Vous avez disposition des indicateurs (hints) permettant dindiquer loptimiseur une stratgie prfre, voire de le forcer utiliser une mthode, un index, et jusqu un plan dexcution prdfini. Certains de ces indicateurs sont intressants, mais ils ne devraient tre utiliss quen cas de rel besoin. Le moteur doptimisation de SQL Server est trs performant, et le forcer se comporter dune certaine manire est en gnral contre-indiqu. Cette mise en garde faite, nous allons prsenter ici ces indicateurs, et tcher dexpliquer quand ils peuvent tre utiliss, et quand ils ne devraient pas ltre. On utilise un indicateur mme la requte, soit la fin de celle-ci avec la clause OPTION() pour les indicateurs de requte (query hints), soit aprs une table avec la clause WITH() pour les indicateurs de tables (table hints). Voici un exemple dutilisation dun indicateur de requte forant un certain algorithme de jointure :
SELECT * FROM Sales.Customer c JOIN Sales.CustomerAddress ca ON c.CustomerID = ca.CustomerID WHERE TerritoryID = 5 OPTION (MERGE JOIN);

Indicateurs de requte
Les indicateurs de requte disponibles sont les suivants :
{ HASH | ORDER } GROUP { CONCAT | HASH | MERGE } UNION { LOOP | MERGE | HASH } JOIN

Ces trois indicateurs permettent de forcer les algorithmes dagrgation (voir section 8.1.1), dUNION et de jointure. vitez de forcer ce choix, de rares exceptions prs, loptimiseur sait ce quil fait.
1. Rfrence sur le Hash Aggregate : http://blogs.msdn.com/craigfr/archive/2006/09/20/hashaggregate.aspx

8.2 Gestion avance des plans dexcution

257

FAST nombre_de_lignes

Prcise que la requte doit tre optimise pour renvoyer dabord un nombre spcifique de lignes au client, avant de poursuivre. Cette clause peut tre utile pour un affichage rapide sur une premire page de pagination. Elle permet, comme la clause TOP, de profiter de la fonctionnalit dobjectif de lignes (row goal), qui peut modifier le plan dexcution en consquence. Cest une bonne chose pour le retour du nombre de lignes indiqu, mais potentiellement au dtriment des performances de la requte entire. Il sagit donc dune option double tranchant, qui peut faire empirer les choses1. Il est prfrable dutiliser la clause TOP, qui garantit le retour du nombre de lignes indiqu.
FORCE ORDER

Spcifie que lordre des tables dans la dclaration de jointure doit tre conserv. Cette option est trs rarement utile, mais si vous avez un soupon de mauvaise gnration de plan, vous pouvez essayer diffrents ordres dapparition des tables dans votre clause FROM, avec cette option, pour voir quelle est la combinaison gagnante. Une fois de plus, un changement structurel de tables, et la modification de la stratgie dindexation, peut changer le plan optimal, qui ne pourra plus alors tre automatiquement choisi par loptimiseur.
MAXDOP nombre_de_processeurs

Vous permet dindiquer un nombre de processeurs maximum impliqu dans une paralllisation de la requte. Rappelons que la paralllisation maximum peut tre dfinie au niveau du serveur par loption maximum degree of parallelism . Cet indicateur vous permet dattribuer une autre valeur une requte spcifique, ce qui peut se rvler utile soit pour dsactiver le paralllisme sur une requte coteuse (MAXDOP 1), soit pour lactiver sur une requte alors quil est dsactiv au niveau du serveur. Rappelons galement que cette option ne signifie pas que SQL Server va ncessairement parallliser la requte. On lui en donne la possibilit, et il fait son choix selon le cot de la requte (selon loption de serveur cost threshold for parallelism ) et la charge actuelle des processeurs de la machine.
OPTIMIZE FOR ( @variable_name = literal_constant [ , ...n ] )

Indique loptimiseur de gnrer un plan optimis pour une valeur de paramtre donne, ce qui est trs utile pour djouer les piges du parameter sniffing (voir section 9.2.1). Il vous suffit dindiquer une valeur distribue de faon reprsentative dans votre colonne, pour viter la compilation avec des plans extrmes. SQL Server 2008 ajoute plus de souplesse cet indicateur : OPTIMIZE FOR UNKNOWN : tous les paramtres sont considrs avec une valeur de distribution moyenne de la colonne. OPTIMIZE FOR ( @variable_name = UNKNOWN) : une valeur de distribution moyenne est considre pour ce paramtre.
1. Voir http://blogs.msdn.com/queryoptteam/archive/2006/03/30/564912.aspx

258

Chapitre 8. Optimisation du code SQL

Cela permet dviter dans tous les cas les valeurs extrmes. En gnral, pour les colonnes qui ont une distribution trs htrogne (quelques valeurs trs slectives, dautres avec beaucoup de doublons), une stratgie de scan sera choisie. Il est inutile de spcifier cet indicateur pour des colonnes avec une contrainte ou un index unique. Dans ce cas le plan na pas de risque dtre mal estim.
RECOMPILE

Force la recompilation systmatique de linstruction. Utile lintrieur dun objet de code. Nous le verrons plus en dtail dans la section 9.2.1.
PARAMETERIZATION { SIMPLE | FORCED }

Force un auto-paramtrage. Nous verrons cet indicateur plus en dtail dans la section 9.3.
KEEP PLAN, KEEPFIXED PLAN

permet de diminuer le nombre de recompilations produites sur une instruction dans un objet de code. Au fil des versions, SQL Server provoque de moins en moins de recompilation en cachant de mieux en mieux les instructions individuellement. KEEP PLAN diminue le nombre de recompilations dues des changements de cardinalit (utile pour une table temporaire, qui va provoquer une recompilation aprs une insertion premire de six lignes), KEEPFIXED PLAN empche une recompilation due des changements de statistiques de distribution. Nous le verrons plus en dtail dans la section 8.3.1.
EXPAND VIEWS

Indique que les index dune vue indexe ne doivent pas tre utiliss pour satisfaire la requte, mais que le plan doit se calculer sur les tables sous-jacentes. Lindicateur de table WITH(NOEXPAND) provoque leffet inverse : seuls les index de la vue sont considrs.
MAXRECURSION nombre_de_rcursions

Limite le nombre dappels rcursifs dans une expression de table (common table expression, CTE) rcursive.
USE PLAN N'xml_plan'

Vous permet de coller un plan dexcution en format XML. Cet indicateur ne peut tre utilis que dans un SELECT. Cest lindicateur le plus dangereux, car il fixe tout le plan dexcution dans le marbre. Il suffit quun lment de la structure de table qui y est rfrenc soit supprim, pour provoquer une erreur dexcution. De mme, une fonctionnalit de plan modifie dans une version ultrieure invalide aussi le plan. Nutilisez cette fonctionnalit quen dernier ressort, quand par exemple vous connaissez un meilleur plan, gnr par une version antrieure de SQL Server, et que la mise jour a dgrad les performances. Vous pouvez rcuprer le plan laide de SET SHOWPLAN_XML, et le passer la requte. Envoyez ce plan en UNICODE, avec le N devant le littral, pour viter des conversions de caractre indsirables lenregistrement du plan. Le plan doit pouvoir tre valid par le schma

8.2 Gestion avance des plans dexcution

259

Showplanxml.xsd, disponible dans le rpertoire dinstallation de SQL Server, ou sur le site de Microsoft. Une option de session : SET FORCEPLAN { ON | OFF }, force galement les plans dexcution de deux faons : les instructions excutes dans la session respecteront lordre dapparition des tables dans la clause FROM, et loprateur de jointure en boucle imbrique (nested loop) sera partout utilis, moins quun indicateur force un autre algorithme. Autant dire que cette option est laisser OFF.

Indicateurs de table
Les indicateurs de table sajoutent aprs la dclaration de table, dans une clause FROM, comme ceci :
SELECT FirstName, LastName FROM Person.Contact WITH (READUNCOMMITTED);

Nous en listons ici les principaux. La plupart concernent les verrouillages, nous ne les abordons que succinctement, entrant plus en dtail sur le sujet dans le chapitre 7. FORCESEEK : exclusivement en SQL Server 2008 : force une stratgie de seek dindex plutt quun scan. NOEXPAND : force lutilisation des index sur une vue indexe. INDEX ( index_val [ ,...n ] ) :force lutilisation dun index (mme sil na rien voir avec le critre de filtre de la clause WHERE. HOLDLOCK : force le niveau disolation SERIALIZABLE pour la table. NOLOCK : force le niveau disolation READ UNCOMMITTED pour la table. NOWAIT : dsactive lattente de verrous. Si la table est verrouille lexcution de la requte, SQL Server renvoie une erreur immdiatement. PAGLOCK : force le choix dune granularit de verrous par pages, au lieu dune granularit par ligne ou par table. READCOMMITTED : force le niveau disolation READ COMMITTED pour la table (niveau disolation par dfaut de SQL Server), utilise le row versioning si la base est en mode READ_COMMITTED_SNAPSHOT. READCOMMITTEDLOCK : force le niveau disolation READ COMMITTED pour la table, sans utiliser le row versioning mme si la base est en mode READ_COMMITTED_SNAPSHOT. READPAST : force la lecture dune table sans attendre la libration des verrous incompatibles. Cela peut donc entraner une lecture incomplte, mais rapide (on nobtient donc que largent du beurre, mais pas forcment le beurre). Par exemple, le code suivant ne va lire que quatre lignes sur cinq (les nombres 1, 2, 4, 5) :
CREATE TABLE dbo.ReadPast (nombre int not null) INSERT INTO dbo.ReadPast (nombre) VALUES (1) INSERT INTO dbo.ReadPast (nombre) VALUES (2)

260

Chapitre 8. Optimisation du code SQL

INSERT INTO dbo.ReadPast (nombre) VALUES (3) INSERT INTO dbo.ReadPast (nombre) VALUES (4) INSERT INTO dbo.ReadPast (nombre) VALUES (5) BEGIN TRAN UPDATE dbo.ReadPast SET nombre = -nombre WHERE nombre = 3 -- dans une autre session SELECT * FROM dbo.ReadPast WITH (READPAST)

READUNCOMMITTED : force le niveau disolation READ UNCOMMITTED pour la table, strictement quivalent NOLOCK. REPEATABLEREAD : force le niveau disolation REPEATABLE READ pour la table. Ce nest donc intressant que dans une transaction explicite. ROWLOCK force le choix dune granularit de verrous par ligne (granularit par dfaut de SQL Server), empche donc normalement lescalade. Sans garantie. Par exemple, en niveau disolation SERIALIZABLE, des verrous plus larges doivent de toute faon tre poss. SERIALIZABLE : force le niveau disolation SERIALIZABLE pour la table, strictement quivalent HOLDLOCK. TABLOCK : force le choix dune granularit de verrou par table. Pose un verrou partag (S). TABLOCKX : force le choix dune granularit de verrou par table. Pose un verrou exclusif (X). UPDLOCK : force un verrouillage de mise jour (U). XLOCK : force un verrouillage exclusif (X).

8.2.2 Guides de plan


Les guides de plan permettent de forcer des indicateurs de requtes sur une instruction sans la modifier directement. Ils sont utiles pour appliquer des indicateurs des requtes sur lesquelles vous navez pas la main. Vous crez simplement un guide de plan laide de la procdure stocke systme sp_create_plan_guide, et vous modifiez, dsactivez ou activez ce guide par la procdure sp_control_plan_guide. Vous pouvez crer trois types de guides : un guide pour une instruction dans un OBJET de code : procdure stocke, fonction utilisateur, dclencheur ; un guide SQL, pour des requtes ad hoc ; un guide de type TEMPLATE, pour modifier le comportement dautoparamtrage dune classe de requtes. Ce type ne permet que de modifier lindicateur PARAMETERIZATION de la requte.

8.2 Gestion avance des plans dexcution

261

Vous spcifiez le type dans le paramtre @type la cration du guide. Voici un exemple dutilisation de guide de plan :
CREATE PROCEDURE dbo.GetContactsForPlanGuide @LastName nvarchar(50) = NULL AS BEGIN SET NOCOUNT ON SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE @LastName; END GO EXEC dbo.GetContactsForPlanGuide 'Abercrombie' EXEC dbo.GetContactsForPlanGuide '%' -- pas bon GO EXEC sys.sp_create_plan_guide @name = N'Guide$GetContactsForPlanGuide$OptimizeForAll', @stmt = N'SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE @LastName', @type = N'OBJECT', @module_or_batch = N'dbo.GetContactsForPlanGuide', @params = NULL, @hints = N'OPTION (OPTIMIZE FOR (@LastName = ''%''))' EXEC dbo.GetContactsForPlanGuide '%' -- mieux ! GO -- suppression SELECT * FROM sys.plan_guides EXEC sys.sp_control_plan_guide @Operation = N'DROP', @Name = N'Guide$GetContactsForPlanGuide$OptimizeForAll'

Dans cet exemple, nous avons cr une procdure stocke qui est excute la premire fois avec un paramtre provoquant une compilation de plan dexcution utilisant un seek dindex, par parameter sniffing (voir section 9.2.1). Le deuxime appel utilisera un plan dexcution trs dfavorable. Nous crons ensuite un guide de plan qui utilise lindicateur OPTIMIZE FOR pour forcer une compilation prenant en compte une valeur de paramtre gnrique (un scnario du pire, qui sera dsagrable pour les valeurs de paramtre qui profiteraient dun index, cause de leur grande slectivit, mais qui limitera les dgts en cas denvoi de paramtre trop peu slectif). Pour supprimer le guide de plan, nous utilisons le paramtre @Operation = N'DROP' en appel de sp_control_plan_guide. Nous pourrions aussi dsactiver le plan par la valeur 'DISABLE'.

262

Chapitre 8. Optimisation du code SQL

En SQL Server 2008, les vnements de trace Performance : Plan Guide Successful Event et Performance : Plan Guide Unsuccessful Event vous indiquent les plans dexcutions crs laide dun guide, ou si la cration du plan a chou. De mme, dans SSMS, vous trouvez la liste des guides de plan dans lexplorateur dobjets, sous le nud Programmability .

La vue systme sys.plan_guides liste les guides de plan enregistrs dans la base courante.

8.3 OPTIMISATION DU CODE SQL


Un SGBDR est un serveur dont le travail est dassurer un stockage des donnes optimal et cohrent. Il est matre de ses donnes. SQL est un langage de requte (le nom est bien choisi) dont lexpression est dclarative : via une requte SQL, le programmeur dcrit le rsultat dsir. Par exemple, une requte comme celle-ci :
SELECT FirstName, LastName FROM Person.Contact WHERE Title = 'Mr.' ORDER BY LastName, FirstName;

est purement descriptive : on y demande une liste des prnoms et noms de contacts, lorsque leur titre est Mr. , tris par nom et prnom. aucun moment, on indique au serveur comment produire cette liste. Tout fait comme lorsque vous allez dans une librairie commander un livre, vous demandez simplement votre libraire de passer commande dun ouvrage, vous ne lui dites pas : pourriez-vous faire une recherche dans votre catalogue de livres distribus, pour noter le numro ISBN du titre que je souhaite obtenir, pour le copier-coller dans votre programme de commande, et y inscrire mon nom. Ensuite, merci dimprimer un bon de commande pour le faire parvenir par voie postale au distributeur de ces ditions. Votre libraire est bien plus qualifi que vous pour effectuer cette tche, et dailleurs, si vous le forciez utiliser votre mthode, la commande prendrait peuttre plus de temps, ou mme ne serait-elle jamais faite correctement. SQL Server fonctionne de la mme faon. Si la syntaxe du langage est dclarative, son excution sera finalement procdurale : le moteur doptimisation, qui fait partie du moteur relationnel, est un bijou dalgorithmique. La stratgie de plan dexcution se base sur les connaissances dont dispose le moteur doptimisation sur la structure et le contenu des tables et des vues. Le travail du programmeur SQL est dcrire la requte la plus descriptive possible, pour donner linformation prcise de ce que la requte doit obtenir. Durant la phase de compilation, le moteur relationnel a une grande capacit normaliser les syntaxes quivalentes pour produire larbre dexcution. Par exemple, ces requtes produisent (presque) le mme plan (sur SQL Server 2005) :
SELECT c.FirstName, c.LastName FROM Person.Contact c CROSS JOIN HumanResources.Employee e

8.3 Optimisation du code SQL

263

WHERE c.ContactId = e.ContactId GO SELECT c.FirstName, c.LastName FROM Person.Contact c JOIN HumanResources.Employee e ON c.ContactId = e.ContactId GO SELECT c.FirstName, c.LastName FROM Person.Contact c WHERE EXISTS (SELECT * FROM HumanResources.Employee e WHERE c.ContactId = e.ContactId) GO SELECT c.FirstName, c.LastName FROM Person.Contact c WHERE c.ContactId IN (SELECT e.ContactId FROM HumanResources.Employee e) GO

Les deux premiers plans sont identiques, les deux suivants aussi, qui incluent un tri (distinct sort). Le nombre de reads est identique, et considrant la faible volumtrie (290 lignes retournes), les performances sont, dans les faits, les mmes, mme si les deux derniers plans sont plus coteux. Il nest donc souvent pas utile de choisir une syntaxe lgrement diffrente dune autre pour amliorer les performances. Testez les plans dexcution, et choisissez la syntaxe la plus intuitive et la plus lisible. De mme, lordre dapparition des colonnes dans le SELECT, des tables dans la clause FROM et des expressions dans les clauses WHERE et HAVING, na pas dimportance. La requte sera de toute manire dcompose et rcrite durant la phase de compilation. Sur certains SGBDR, lordre des tables dans la jointure influe sur les performances. Ce nest pas le cas en SQL Server. Par contre, lordre des colonnes dans les clauses GROUP BY et ORDER BY peut modifier les performances, en influant sur les tris, oprations coteuses.

criture ensembliste
Nous lavons dit, le langage SQL est dclaratif. Il est toutefois aussi ensembliste : toutes les instructions dfinissent et travaillent sur des ensembles de donnes, avec parfois des entorses lalgbre relationnelle (la clause ORDER BY par exemple est une hrsie pour le modle relationnel, o les tuples dune relation nont aucun ordre prcis. SQL permet de gnrer un jeu de rsultat tri, ce qui viole la thorie relationnelle, mais est trs utile). Il est donc important dviter le plus possible des syntaxes non dclaratives et non ensemblistes, le plus triste exemple tant le curseur. Les boucles WHILE entrent dans ce cas de figure. Nous avons galement dit que, bien que le langage soit ensembliste, lexcution est procdurale : le moteur de stockage doit finalement parcourir les lignes ou les cls dun index une une. Ce simple exemple suffit la dmontrer :
DECLARE @i int; SET @i = 0; SELECT @i = @i + 1 FROM Person.Contact; SELECT @i;

264

Chapitre 8. Optimisation du code SQL

aprs excution, @i vaut 19973, ce qui est la cardinalit de la table Person.Contact. Pour des considrations de performances, cela signifie une chose : mme si la requte est crite en pur SQL, elle peut tre plus ou moins efficace selon la stratgie dcide par loptimiseur. Certaines constructions SQL permettent des stratgies optimales, dautres forcent le plan utiliser des oprations proches dun fonctionnement de curseur. Cest notamment le cas de syntaxes introduites dans SQL Server 2005, comme la clause PIVOT et les fonctions de fentrage. Leur utilit est donc balancer avec leur cot, et elles sont bannir autant que possible, surtout lorsquil existe une syntaxe plus traditionnelle. Prenons un exemple. Nous voulons obtenir une liste des contacts avec, pour chaque ligne, le nombre total de personnes portant le mme nom de famille. Nous pouvons utiliser une sous-requte ou une fonction de fentrage :
-- SELECT avec sous-requte SELECT t.FirstName, t.LastName, (SELECT COUNT(*) FROM Person.Contact WHERE LastName = t.LastName) cnt FROM Person.Contact t GO -- SELECT avec fonction de fentrage SELECT t.FirstName, t.LastName, COUNT(*) OVER (PARTITION BY LastName) as cnt FROM Person.Contact t GO

Nous voyons le rsultat de la trace sur la figure 8.10. La requte utilisant la sousrequte a ncessit environ 1 200 lectures, alors que celle utilisant la fonction de fentrage a lu 47 000 pages, et en a crit 12. La raison de ces lectures et criture est la cration dune table de travail (oprateur table spool). lheure actuelle, dans SQL Server 2005, la premire requte est donc bien plus performante.

Figure 8.10 Diffrence de performances

Ce choix entre une syntaxe ou une autre na pas de valeur absolue, et cest pour cette raison que nous disons lheure actuelle . Comme loptimiseur SQL volue, il est possible que le plan dexcution de la fonction de partitionnement samliore dans le futur. Il parat toutefois peu probable quil finisse par dpasser en performances la syntaxe utilisant la sous-requte. La rgle de base quon peut en dduire est

8.3 Optimisation du code SQL

265

celle-ci : lorsque cest possible, choisissez la syntaxe la plus intuitive et la plus traditionnelle . La clart aide loptimiseur, et les syntaxes plus anciennes, bien connues, ont plus de chance de bnficier doptimisations solides.

Comparaisons et filtres
Dans la clause WHERE dune requte, comme dans la clause ON dune jointure, peuvent tres exprimes des expressions de filtre. Ces expressions retournent un rsultat en logique ternaire : VRAI, FAUX ou INCONNU. Elles sont composes dun oprateur et de deux oprandes ( gauche et droite de loprateur). La plupart du temps, un des oprandes au moins est une colonne de table. Lautre oprande est la valeur recherche. Par exemple :
SELECT LastName, FirstName FROM Person.Contact WHERE LastName LIKE 'Al%';

Si la colonne recherche est indexe, loptimiseur va peut-tre choisir dutiliser lindex dans le plan dexcution, selon la slectivit de celui-ci. Si les estimations de loptimiseur lui montrent quun scan de la table ou dun index sera moins coteux en lectures quune recherche dans les cls de lindex, le scan sera choisi. Cela dpend du nombre de lignes retournes par la requte. Quoi quil en soit, en labsence dun index utile, SQL Server sera oblig dexcuter un scan. Considrez donc la cration dindex sur les colonnes prsentes dans vos expressions de filtre, et testez aprs cration si lindex est utilis par la requte. Cette rgle est bien entendu valable pour les expressions de jointure :
SELECT c.LastName, c.FirstName, a.AddressLine1, a.PostalCode, a.City FROM Person.Contact c JOIN HumanResources.Employee e ON c.ContactId = e.ContactId JOIN HumanResources.EmployeeAddress ea ON e.EmployeeId = ea.EmployeeId JOIN Person.Address a ON ea.AddressId = a.AddressId;

Dans cet exemple, les expressions de la clause ON des jointure effectuent des recherches sur les colonnes tout comme la clause WHERE. Elles bnficient donc de la prsence dindex. Les cls des tables mres sont dj indexes : en rgle gnrale, une jointure est effectue entre une ou plusieurs colonnes lies par une contrainte de cl trangre (bien que rien dans le langage SQL ne force la jointure sappuyer sur une telle dfinition de modle). La cl trangre ne peut sappuyer que sur une cl primaire ou unique de la table mre. Ces cls crent ncessairement des index. Par exemple, La colonne ContactId de la table Person.Contact est cl primaire clustered. Par contre la cration de cl trangre en SQL Server ne force en rien la cration dun index sur la cl dporte dans la table fille. Cest vous de le faire, et cest pratiquement une tape ncessaire pour assurer la bonne performance des requtes de jointure. Pour quune expression de filtre utilise lindex, il est impratif que la colonne soit prsente comme oprande, sans aucune modification. Cest ce quon appelle un SARG (Search ARGument). Un seek dindex ne peut tre valu comme possibilit que sur un oprande de type SARG, cest--dire qui correspond exactement ce qui

266

Chapitre 8. Optimisation du code SQL

est stock dans la cl de lindex. Toute modification de colonne dans lexpression, comme par exemple une concatnation, un passage dans une fonction, ou un changement de collation la vole, empchera toute utilisation dindex, et forcera par consquent le scan. Cest donc viter. Exemples de choses ne pas faire :
SELECT FirstName, LastName FROM Person.Contact WHERE LastName COLLATE Latin1_General_CI_AI = 'Jimenez'; SELECT FirstName, LastName FROM Person.Contact WHERE LEFT(LastName, 2) = 'AG'; SELECT FirstName, LastName FROM Person.Contact WHERE LastName + FirstName = 'AlamedaLili';

crivez donc vos filtres plutt comme ceci :


SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE 'Jim[e]nez'; SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE 'AG%'; SELECT FirstName, LastName FROM Person.Contact WHERE LastName = 'Alameda' AND FirstName = 'Lili';

Loprateur LIKE permet lutilisation de lindex, condition que le dbut de la chane soit fixe. Il correspond ainsi au dbut de la cl de lindex, et est donc SARGable, comme on peut chercher efficacement dans un annuaire en ne connaissant que les quelques premires lettres dun nom. Cette rgle est valable aussi pour les oprations arithmtiques. SQL Server nest pas capable de modifier une expression contenant des oprations sur des constantes pour la simplifier, une opration connue en compilation sous le nom de constant folding. Prenons un exemple :
SELECT * FROM Person.Contact WHERE ContactId + 3 = 34; SELECT * FROM Person.Contact WHERE ContactId = 37;

Il nest pas trs difficile pour un compilateur de transformer automatiquement la premire clause en la seconde. Pourtant, SQL Server ne le fait pas, comme nous le voyons dans les plans dexcution en figure 8.11.

8.3 Optimisation du code SQL

267

Figure 8.11 Diffrence de plans dexcution

La rgle suivre est donc celle-ci : une des deux oprandes doit tre la colonne, seulement la colonne, et rien que la colonne. Dans la liste des oprateurs disponibles, lgalit (=) est le plus utilis. Cest celui qui offre le plus de possibilit doptimisation par un index. Loprateur de diffrence (<>) est souvent le moins efficace, non pas parce quil empche en tant que tel lutilisation dun index, mais parce quil provoque souvent la slection dun nombre important de lignes. En ralit, les oprateurs qui provoquent une recherche de plage, sont manier avec prcaution, car ils peuvent entraner le retour dun grand nombre de lignes. Ces oprateurs sont <, >, <=, >=, BETWEEN et <>.

Grandeur et misre des fonctions utilisateur


Les fonctions utilisateur (User Defined Function, UDF) sont des objets de code, au mme titre que les procdures stockes et les dclencheurs. Leur travail est de retourner une valeur scalaire ou une table au code T-SQL appelant. Elles peuvent sembler trs pratiques au premier abord, car elles permettent de raliser le rve de tout programmeur : la diminution du code redondant par modularisation. Avonsnous besoin de faire une recherche rptitive sur une table, pourquoi ne pas encapsuler cette recherche dans une fonction ? Parce que ce qui parat premire vue une bonne ide pour le programmeur se rvle une trs mauvaise ide pour un SGBDR. Nous pourrions songer modulariser notre code de la faon suivante : au lieu dutiliser des sous-requtes ou des jointures, nous serions tents daller chercher les

268

Chapitre 8. Optimisation du code SQL

valeurs ou les calculs qui nous intressent laide dune fonction. En reprenant notre exemple du compte de noms de famille, essayons de modulariser le code, pour rutiliser ce calcul dans toutes circonstances. Nous crons alors la fonction suivante :
CREATE FUNCTION Person.GetCountContacts (@LastName nvarchar(50)) RETURNS int AS BEGIN RETURN (SELECT COUNT(*) FROM Person.contact WHERE LastName LIKE @LastName) END;

Nous profitions ainsi dune fonction qui retourne le compte des contacts pour toute la table (paramtre '%'), par dbut de nom (paramtre 'A%' par exemple, ou par nom (paramtre 'Ackerman' par exemple) :
SELECT Person.GetCountContacts('%'); SELECT Person.GetCountContacts('A%'); SELECT Person.GetCountContacts('Ackerman');

Pratique, non ? Peut-tre dans ce contexte-l, mais gardons-nous dinclure cette fonction dans la clause SELECT dune requte. Observons les diffrences de performances entre deux faons diffrentes dobtenir les mmes rsultats :
-- SELECT avec utilisation de la fonction SELECT t.FirstName, t.LastName, Person.GetCountContacts(t.LastName) as cnt FROM Person.Contact t GO -- SELECT avec sous-requte SELECT t.FirstName, t.LastName, (SELECT COUNT(*) FROM Person.Contact WHERE LastName = t.LastName) cnt FROM Person.Contact t GO

Nous avons trac ces requtes. Voyons le rsultat sur la figure 8.12.

Figure 8.12 Trace des requtes

La requte utilisant la fonction dure huit secondes et lit prs de 46 000 pages, alors que la solution avec sous-requte, une seconde (125 millisecondes de temps CPU) pour 1 200 pages lues. La diffrence est sans appel. Pourquoi cela ? Parce

8.3 Optimisation du code SQL

269

quavec la fonction, nous forons SQL Server utiliser la stratgie de requte que nous avons dcide. Pour chaque ligne de la table Person.Contact implique dans la requte, SQL Server doit entrer dans la fonction et excuter la requte qui sy trouve nouveau. Puisque nous avons spar le code en deux objets, loptimiseur na aucun moyen de lier les deux requtes pour comprendre ce quon veut obtenir. Ainsi la nature dclarative du langage SQL est viole. En revanche, en incluant avec une sous-requte les deux oprations dans la mme requte, loptimiseur a tout en main pour la dcortiquer, la comprendre, et loptimiser. Regardons le plan dexcution (un peu nettoy pour faciliter sa lecture) de lordre utilisant la sous-requte :
|--Compute Scalar |--Hash Match(Right Outer Join ...) |--Compute Scalar ... | |--Stream Aggregate( Count(*))) | |--Index Scan(OBJECT:(... [nix$Person_Contact$LastName]), ...) |--Clustered Index Scan(OBJECT:(... [PK_Contact_ContactID] AS [t]))

La conclusion est simple : mfiez-vous des fonctions.

Comptage des lignes dune table


Pour compter le nombre total de lignes dune table, vous ntes pas oblig de faire un COUNT. Vous pouvez vous appuyer sur les informations de mtadonnes, qui sont, autant que nous avons pu en faire lexprience, toujours jour. videmment, cette astuce nest valable que si vous voulez la cardinalit de toute la table, non filtre. Dmonstration :
SELECT COUNT(*) FROM Person.Contact; GO SELECT SUM(row_count) as row_count FROM sys.dm_db_partition_stats WHERE object_id=OBJECT_ID('Person.Contact') AND (index_id=0 or index_id=1); GO

Rsultat en reads :
Table 'Contact'. Scan count 1, logical reads 46, -- COUNT(*) Table 'sysidxstats'. Scan count 2, logical reads 4, -- sys.dm_db_partition_stats

Plan dexcution : figure 8.13.

270

Chapitre 8. Optimisation du code SQL

Figure 8.13 Plans dexcution

8.3.1 Tables temporaires


Les tables temporaires, bien quelles soient dclares dans tempdb, restent en mmoire, dans le buffer, jusqu ce quelles atteignent une taille qui ncessite leur criture sur disque.

propos des variables de type table


SQL Server 2000 a introduit la variable de type table, cest--dire la possibilit de stocker une table dans une variable Transact-SQL. Cette facilit est utile pour renvoyer une table partir dune fonction utilisateur. Elle peut aussi tre utilise lintrieur dune procdure stocke pour remplacer une table temporaire. Vous entendrez parfois dire que la variable de type table est bien plus lgre quune table temporaire car elle nest pas crite dans tempdb et ne vit quen mmoire. Cest erron. La variable de type table est elle aussi stocke dans tempdb, comme une table temporaire. SQL Server lui assigne un nom interne. Il est trs simple de le dmontrer :
SELECT * FROM tempdb.INFORMATION_SCHEMA.TABLES GO DECLARE @t TABLE (id int) SELECT * FROM tempdb.INFORMATION_SCHEMA.TABLES GO

Vous verrez dans le deuxime apparatre une nouvelle rfrence de table dans les mtadonnes de tempdb, avec un nom ressemblant ceci (exemple lors de notre excution) : #04CA11FE.

8.3 Optimisation du code SQL

271

Notez que si vous ne sparez pas les deux tests par un GO, vous verrez la table dans les deux SELECT, le DECLARE tant plac en premier par la compilation du code SQL.

Comme pour une table temporaire, la table contenue dans la variable rside en mmoire jusquau moment o elle atteint une certaine taille. Elle est ensuite crite dans tempdb. Le gain apport par les variables de type table est donc limit. Y a-t-il alors une diffrence entre les deux options ? Oui, la variable de type table est plus rapide principalement parce quelle consomme moins de ressources pour la maintenir : il ny a pas de calcul de statistiques de colonnes sur une variable de type table par exemple, le verrouillage des lignes est moindre, et ne dure que le temps de linstruction de modification, de mme que la transaction de modification, mme si elle est journalise (il suffit pour le constater dutiliser la fonction fn_dblog() que nous avons dj utilise), nest pas enrle dans une transaction explicite, elle sexcute et est valide automatiquement dans sa propre transaction prive . Ces avantages peuvent aussi se rvler des inconvnients, selon lutilisation quon veut faire de la variable de type table. Labsence de calcul de statistiques sur les colonnes peut tre contre-productive sur des variables de type table volumineuses. Comme loptimiseur na aucune statistique, son plan dexcution va se baser sur lestimation du retour de zro ou dune seule ligne, quelle que soit la ralit. On peut le vrifier simplement, par exemple en regardant le plan dexcution estim de cette requte (qui retourne en ralit 1 763 lignes sur ma version de la base resources) :
DECLARE @t TABLE (Name SYSNAME) INSERT @t SELECT name FROM sys.system_objects SELECT * FROM @t

Figure 8.14 Estimation requte table variable Le plan dexcution gnr peut donc se rvler bien moins performant quavec une table temporaire.

Il est de mme impossible de crer des index sur une variable ( part un index li une contrainte : dclaration de cl primaire ou de cl unique, possibles unique-

272

Chapitre 8. Optimisation du code SQL

ment la cration de la table), comme il est impossible dy appliquer aprs cration toute commande DDL. Ainsi, les variables ne doivent pas tre utilises pour remplacer des tables temporaires volumineuses sur lesquelles on effectue beaucoup doprations de recherche. Nous lavons dit, les oprations sur la variable ne sont pas enrles dans une transaction. La diffrence est simple exprimenter :
USE tempdb GO CREATE TABLE #t (Name SYSNAME) BEGIN TRAN INSERT #t SELECT name FROM [master].[dbo].[sysobjects] ROLLBACK SELECT * FROM #t GO DECLARE @t TABLE (Name SYSNAME) BEGIN TRAN INSERT @t SELECT name FROM [master].[dbo].[sysobjects] ROLLBACK SELECT * FROM @t GO

Dans la premire partie du code, nous utilisons une table temporaire, dans laquelle nous insrons des lignes lintrieur dune transaction explicite. Aprs lannulation de la transaction (ROLLBACK), la table est vide : linsertion a t annule. En revanche, lapplication du mme code la variable de type table montre que, mme aprs le ROLLBACK, les lignes insres sont toujours dans la variable. Bien que bnfique pour les performances, ce comportement est videmment dangereux : si vous codez des transactions impliquant des variables de type table, vous risquez dobtenir des rsultats inattendus. En conclusion, Microsoft recommande de remplacer les tables temporaires par des variables autant que possible. Prcisons donc quelles ne sont intressantes que lorsquelles sont de petite taille, pour des oprations simples, et bien entendu, lorsquon ne peut les remplacer par des jointures, des sous-requtes ou des expressions de table.

8.3.2 Pour ou contre le SQL dynamique


Le sujet fait dbat. Que penser du SQL dynamique ? Cest ainsi quest appele la possibilit de gnrer une instruction SQL, plus ou moins complexe, dans une

8.3 Optimisation du code SQL

273

variable de type VARCHAR, pour ensuite lvaluer et lexcuter dynamiquement laide du mot-cl EXEC(UTE). Par exemple :
DECLARE @sql varchar(8000) SET @sql = 'SELECT * FROM Person.Contact' EXEC (@sql)

Cette syntaxe particulire est frquemment utilise pour construire, lintrieur de procdures stockes, des requtes complexes, notamment pour permettre une combinaison de multiples critres de recherches dans la clause WHERE. Imaginons le cas dun site web qui permet, dans une page de formulaire, deffectuer une recherche multicritres sur des produits. Chaque argument de recherche correspond une colonne, le tout sur une ou plusieurs tables de notre base. Linternaute peut saisir ou non une valeur pour chaque argument. Une procdure stocke reoit en paramtre la liste des arguments, quelle utilise pour filtrer la requte. Il ny a pratiquement que deux choix : crire une requte traditionnelle qui prend en compte tous les cas de figure, comme ceci :
SELECT FirstName, MiddleName, LastName, Suffix, EmailAddress FROM Person.Contact WHERE (LastName = @LastName OR @LastName IS NULL) AND (FirstName = @FirstName OR @FirstName IS NULL) AND (MiddleName = @MiddleName OR @MiddleName IS NULL) AND (Suffix = @Suffix OR @Suffix IS NULL) AND (EmailAddress = @EmailAddress OR @EmailAddress IS NULL) ORDER BY LastName, FirstName

ou gnrer son code SQL de faon dynamique, avec une construction comme celle-ci :
DECLARE @sql varchar(8000) SET @sql = ' SELECT FirstName, MiddleName, LastName, Suffix, EmailAddress FROM Person.Contact WHERE ' IF @FirstName IS NOT NULL SET @sql = @sql + ' FirstName = ''' + @FirstName + ''' AND ' IF @MiddleName IS NOT NULL SET @sql = @sql + ' MiddleName = ''' + @MiddleName + ''' AND ' IF @LastName IS NOT NULL SET @sql = @sql + ' LastName = ''' + @LastName + ''' AND ' IF @Suffix IS NOT NULL SET @sql = @sql + ' Suffix = ''' + @Suffix + ''' AND ' IF @EmailAddress IS NOT NULL SET @sql = @sql + ' EmailAddress = ''' + @EmailAddress + ''' AND ' SET @sql = LEFT(@sql, LEN(@sql)-3) EXEC (@sql)

Le SQL dynamique a ses dfauts : intgr dans une procdure stocke, il annule en quelque sorte les avantages de prcompilation de la procdure. En effet, la chane

274

Chapitre 8. Optimisation du code SQL

excute ne fait plus partie de la procdure stocke. Son valuation et son excution dynamique sont prises en charge par le moteur relationnel comme les autres requtes ad hoc.

Scurit Une autre contrainte vient de la scurit : le SQL dynamique nobit plus aux rgles de chanage de propritaires. En dautres termes, les privilges doivent tre vrifis sur les objets contenus dans le SQL dynamique, et lutilisateur excutant la procdure doit donc tre autoris sur ces objets. Ceci pour viter les risques dinjection SQL, cest--dire denvoi de code malicieux. Vous pouvez crer votre procdure avec la clause EXECUTE AS pour lexcuter dans un contexte dutilisateur ayant des privilges sur ces objets. Soyez nanmoins trs prudents sur les risques dinjections (voyez par exemple cet article, pour vous prvenir contre elles : http://fr.wikipedia.org/wiki/Injection_SQL).

quoi bon faire une procdure stocke si cest pour la faire agir comme un code client ? Mais dans le cas de figure du formulaire de recherche, cet inconvnient se rvle plutt un avantage. La non-rutilisation du plan dexcution permet dviter lcueil dans lequel nous tombons dans le cas de lexemple de requte statique utilisant le bricolage (LastName = @LastName OR @LastName IS NULL). Nous avons cr deux procdures stockes dont vous trouverez le source sur le site daccompagnement du livre. Elles sont nommes Person.SearchContactsStatique et Person.SearchContactsDynamique, et elles implmentent la mme requte selon les deux solutions proposes. Essayons deux appels :
EXEC Person.SearchContactsStatique @LastName = 'Adams' GO EXEC Person.SearchContactsDynamique @LastName = 'Adams' GO EXEC Person.SearchContactsStatique @LastName = 'Adams', @MiddleName = 'S' GO EXEC Person.SearchContactsDynamique @LastName = 'Adams', @MiddleName = 'S' GO

Sur la figure 8.15, nous voyons la diffrence de performance entre les deux solutions, lors du deuxime appel. Elle est trs parlante. La solution dynamique gagne haut la main. Nous voyons aussi un CacheHit : le code ad hoc de la requte dynamique sest tout de mme insr dans le cache de plans. Sur les figures 8.16 et 8.17, nous voyons respectivement le plan de la premire procdure qui na pas chang, et le plan de la seconde qui sest adapt.

8.3 Optimisation du code SQL

275

Figure 8.15 Diffrence de performances

Figure 8.16 Plan de la procdure statique

Figure 8.17 Plan de la procdure dynamique

276

Chapitre 8. Optimisation du code SQL

Nous pourrions conserver la syntaxe de la premire procdure, et forcer la recompilation, mais la requte, comportant de multiples variables, reste difficile analyser. Parfois, la syntaxe dynamique, malgr ses dfauts, en terme de scurit et de lisibilit, est intressante pour les performances.

8.3.3 viter les curseurs


Le curseur est une construction T-SQL permettant de traiter squentiellement un jeu de rsultat ct serveur. Parce quil viole la syntaxe ensembliste du langage SQL, et ainsi court-circuite toute possibilit doptimisation par SQL Server, il est lennemi public numro un des performances du code SQL. En prsence de curseurs, la meilleure dcision doptimisation prendre est de les remplacer par du code ensembliste, ce qui est la plupart du temps possible. Bien que les curseurs soient en euxmmes optimisables (par le choix de curseurs en type READ ONLY ou FAST_FORWARD notamment), nous pensons prfrable de prsenter des moyens de sen passer. La plupart des curseurs quon peut rencontrer dans du code de production sont crs par manque de connaissance des possibilits du langage SQL. Le code dadministration est un problme diffrent. On y rencontre des curseurs pour automatiser des maintenances sur les objets, ce qui est acceptable, compte tenu de la frquence dutilisation, et du choix de fentres dadministration favorables. Souvent, les curseurs sont utiliss pour effectuer des traitements ou des vrifications complexes sur les lignes. Ils peuvent en gnral tre remplacs par de simples jointures, ou un passage par une table temporaire. Le but est de les rcrire en code ensembliste. Si le traitement ligne par ligne est nanmoins ncessaire, il est souvent plus intressant deffectuer une boucle WHILE avec des SELECT, qui se rvlent plus efficaces. Voici un exemple simple de curseur, et son remplacement par une boucle WHILE :
-- curseur DECLARE cur CURSOR FAST_FORWARD FOR SELECT ContactId FROM Person.Contact ORDER BY ContactId DECLARE @CurrentContactID int OPEN cur FETCH NEXT FROM cur INTO @CurrentContactID WHILE (@@fetch_status <> -1) BEGIN IF (@@fetch_status <> -2) PRINT @CurrentContactID FETCH NEXT FROM cur INTO @CurrentContactID END CLOSE cur DEALLOCATE cur GO

8.3 Optimisation du code SQL

277

-- boucle DECLARE @CurrentContactID int SELECT TOP 1 @CurrentContactID = ContactId FROM Person.Contact ORDER BY ContactId WHILE 1 = 1 BEGIN PRINT @CurrentContactID SELECT TOP 1 @CurrentContactID = ContactID FROM Person.Contact WHERE ContactID > @CurrentContactID IF @@ROWCOUNT = 0 BREAK END -- WHILE GO

Mais les boucles ne sont pas toujours ncessaires. SQL est un langage dclaratif et ensembliste, mais lexcution de la requte par le moteur dexcution est au final squentielle : il faut bien finir par parcourir les lignes lune aprs lautre. Cette caractristique peut tre mise profit. Voici par exemple comment raliser une concatnation :
DECLARE @str VARCHAR(MAX) SELECT @str = COALESCE(@str + ', ', '') + LastName FROM Person.Contact GROUP BY LastName ORDER BY LastName SELECT @str

l'aide des fonctions SQL et des jointures, vous pouvez aussi raliser des sparations et des pivots. Par exemple :
CREATE TABLE #citations ( auteur varchar(50), phrase varchar (1000) ) INSERT INTO #citations SELECT 'Guitry', 'Il y a des gens sur qui on peut compter. Ce sont gnralement des gens dont on n''a pas besoin' UNION ALL SELECT 'Cioran', 'Un homme ennuyeux est un homme incapable de s''ennuyer' UNION ALL SELECT 'Talleyrand', 'Les mcontents, ce sont des pauvres qui rflchissent' SELECT auteur, NullIf(SubString(' ' + phrase + ' ' , id , CharIndex(' ' , ' ' + phrase + ' ' , id) - ID) , '') AS mot FROM (SELECT ROW_NUMBER() OVER (ORDER BY NEWID()) as id FROM sys.system_views) tally CROSS JOIN #citations

278

Chapitre 8. Optimisation du code SQL

WHERE id <= Len(' ' + Phrase + ' ') AND SubString(' ' + Phrase + ' ' , id - 1, 1) = ' '

Cette requte place chaque mot rencontr dans la citation sur une nouvelle ligne, dans la colonne mots. Elle se base sur une jointure avec une table de nombres, que nous avons ici gnre la vole. Une table de nombre est toujours utile, faitesen une, laide de ROW_NUMBER(). Pour plus de prcision sur cette mthode de dcoupage, lisez cet article : http://www.sqlteam.com/article/parsing-csv-values-into-multiple-rows. Un autre besoin courant est de produire des totaux cumuls. Cette opration peut tre plus rapide effectue par un curseur que par une requte ensembliste, cest donc un contre-exemple1. Le code ensembliste est essayer toutefois sur des petits totaux cumuls. Voici un exemple de code :
SELECT th1.TransactionID, th1.ActualCost, SUM(th2.ActualCost) AS TotalCumule FROM Production.TransactionHistory th1 JOIN Production.TransactionHistory th2 ON th2.TransactionID <= th1.TransactionID AND th2.TransactionDate = '20030901' WHERE th1.TransactionDate = '20030901' GROUP BY th1.TransactionID, th1.ActualCost ORDER BY th1.TransactionID

Rcursivit
Avant SQL Server 2005, la rcursivit ensembliste ntait pas possible (en tout cas sur des recherches rcursives dont on ne connaissait pas la profondeur). Lexpression de table (Common Table Expression, CTE) rsout ce problme. Exemple dappel rcursif pour descendre dans une hirarchie classique employ/manager :
WITH employeeCTE AS ( SELECT e.EmployeeId, c.FirstName, c.LastName, 1 as niveau, CAST(N'lui-mme' as nvarchar(100)) as boss FROM HumanResources.Employee e JOIN Person.Contact c ON e.ContactID = c.ContactID WHERE e.ManagerID IS NULL UNION ALL SELECT e.EmployeeId, c.FirstName, c.LastName, niveau + 1, CAST(m.FirstName + ' ' + m.LastName as nvarchar(100)) FROM HumanResources.Employee e JOIN Person.Contact c ON e.ContactID = c.ContactID JOIN employeeCTE m ON m.EmployeeId = e.ManagerId ) SELECT FirstName, LastName, niveau, boss FROM EmployeeCTE;
1. Voir cette entre de blog dAdam Machanic, dont lexemple de code est tir : http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/running-sums-redux.aspx

8.3 Optimisation du code SQL

279

En SQL Server 2008, vous disposez du type de donnes HierarchyID, qui est un type complexe implment en classe .NET. Il permet de stocker une information de hirarchie, et de retrouver ces informations travers de mthodes. Malgr le fait que ce soit un type .NET, il est plus rapide que lutilisation de CTE. De plus, on peut indexer ses proprits si ncessaire, en passant par une colonne calcule. Pour vous donner une ide de lutilisation de ce type, voici un exemple de requte, qui fait peu prs la mme chose que la CTE prcdente :
SELECT o1.EmployeeName, o1.EmployeeID.GetLevel() as level, o2.EmployeeName as boss FROM HumanResources.Organization o1 JOIN HumanResources.Organization o2 ON o2.EmployeeID = o1.EmployeeID.GetAncestor(1) WHERE hierarchyid::GetRoot().IsDescendantOf(o1.EmployeeId)= 11

Vous trouvez des exemples dutilisation dans les BOL 2008, sous lentre Working with hierarchyid Data . Une dernire mthode, trs efficace, implique une structuration de la table spcifique, en reprsentation intervallaire. Vous en trouvez la description dans cet article de Frdric Brouard : http://sqlpro.developpez.com/cours/arborescence/.

Mises jour
Un UPDATE seffectue lui aussi ligne par ligne, mme si linstruction est ensembliste. Comme vous pouvez affecter une valeur une variable dans un UPDATE, et mlanger cette affectation avec une mise jour de colonne, vous pouvez gnrer une forme dtourne de boucle, qui vous vitera un curseur. Exemple trs simple :
CREATE TABLE dbo.testloop ( id int NOT NULL IDENTITY (1,1) PRIMARY KEY CLUSTERED, nombre int NULL, groupe int NOT NULL ) GO INSERT SELECT SELECT SELECT GO INTO dbo.testloop (groupe) TOP (10) 1 FROM sys.system_objects UNION ALL TOP (20) 2 FROM sys.system_objects UNION ALL TOP (15) 3 FROM sys.system_objects

DECLARE @i int SET @i = 0 UPDATE dbo.testloop SET @i = @i + 1, nombre = @i GO


1. La mthode IsDescendantOf sappelait IsDescendant en version beta (CTP) 6. Elle a t renomme en IsDescendantOf.

280

Chapitre 8. Optimisation du code SQL

SELECT * FROM dbo.testloop

Cette mthode est relativement peu sre, car elle se base sur une implmentation physique non documente, et potentiellement non consistante au fil des versions. Elle est nanmoins pratique. Depuis SQL Server 2005, les fonctions de fentrage permettent de raliser la mme opration, avec en plus la capacit de grer prcisment un ordre, et un regroupement par la valeur dautres colonnes (cest pour cette raison que nous avons cr la colonne groupe dans la table). Le problme est que nous ne pouvons utiliser une fonction de fentrage que dans un SELECT. Nous pouvons contourner cette limitation laide dune expression de table :
WITH cteTestLoop AS ( SELECT id, ROW_NUMBER() OVER(ORDER BY id) as rownumber FROM dbo.testloop ) UPDATE tl SET nombre = ctl.rownumber FROM dbo.testloop tl JOIN cteTestLoop ctl ON tl.id = ctl.id;

Cet exemple effectue exactement la mme mise jour, mais avec garantie de lordre par la colonne ID. Cette mthode est officielle et sre, par contre vous pourriez encore utiliser la solution base de variable dans les cas o le tri ne vous importe pas, pour des raisons de performance pure. Voyez la diffrence de plan dexcution sur la figure 8.18. La version CTE doit crer un plan compliqu, avec une jointure sur la mme table, un tri et une table de travail (table spool).

Figure 8.18 Plan des requtes de mise jour

8.3 Optimisation du code SQL

281

Mais il faut aussi dire quavec la fonction de partitionnement il est facile de recommencer la numrotation chaque groupe, en modifiant la fonction ROW_NUMBER() dans la CTE ainsi :
WITH cteTestLoop AS ( SELECT id, ROW_NUMBER() OVER(PARTITION BY groupe ORDER BY id) as rownumber FROM dbo.testloop )

Ce qui serait difficile imiter dans notre bricolage , surtout pour garantir un tri correct.

La fonction ROW_NUMBER() demande obligatoirement un ORDER BY. Vous pouvez simplement utiliser nimporte quelle colonne, ou, si vous navez pas de colonne pour gnrer cet ordre, vous pouvez utiliser lastuce suivante : ROW_NUMBER() OVER(ORDER BY (SELECT 0)).

8.3.4 Optimisation des dclencheurs


Les dclencheurs (triggers) sont des blocs de code SQL qui sont excuts au dclenchement dune opration DML sur une table : INSERT, UPDATE ou DELETE. Depuis SQL Server 2005, existent aussi des dclencheurs DDL, qui sexcutent lors de toute modification de structures. Ils nentrent pas dans le cadre de loptimisation du systme, nous ne les aborderons donc pas ici. Du point de vue de la compilation, les dclencheurs se comportent comme des procdures stockes : leur plan dexcution est mis en cache dans le cache de procdure leur premier rappel, et rutilis ensuite. Il ny a donc pas recompilation systmatique, sauf si le code lui-mme provoque des recompilations en modifiant le contexte. Il est important de garder le code lintrieur du dclencheur aussi court et simple que possible : en effet, il sexcute dans la transaction de linstruction qui la dclench, et la prolonge donc dautant, ce qui augmente la dure des verrous. En SQL Server, les dclencheurs sont uniquement ensemblistes (par opposition certains SGBDR qui implmentent des dclencheurs PER ROW ), cela veut dire quils se dclenchent une seule fois par instruction, quel que soit le nombre de lignes affectes. Il est donc essentiel de penser en terme de requtes ensemblistes dans un dclencheur. Il est trs commun de voir du code tel que celui-ci, dans un trigger :
CREATE TRIGGER atr_d$sales_currency$archive ON sales.currency AFTER DELETE AS BEGIN DECLARE @CurrencyCode NCHAR(3), @Name NVARCHAR(50), @DeletedDate smalldatetime

282

Chapitre 8. Optimisation du code SQL

SELECT @CurrencyCode = CurrencyCode, @Name = Name, @DeletedDate = CURRENT_TIMESTAMP FROM Deleted INSERT INTO sales.currencyArchive (CurrencyCode, Name, DeletedDate) VALUES (@CurrencyCode, @Name, @DeletedDate) END

Bien entendu, ceci ne fonctionne correctement que si une seule ligne est supprime. Si toute la table sales.currency est vide, une seule ligne sera inscrite dans la table sales.currencyArchive. Cest une erreur frquente, mfiez-vous en. Dans le contexte du dclencheur, deux pseudo-tables sont disponibles, pour retrouver les lignes affectes par linstruction : DELETED contient les lignes supprimes par un DELETE ou un UPDATE, et INSERTED les lignes ajoutes par un INSERT ou un UPDATE. LUPDATE est considr logiquement comme un DELETE suivi dun INSERT. Ces pseudo-tables sont gres en interne par des versions de ligne stockes dans le version store de tempdb (voir section 4.4). De ce fait, le dclenchement dun trigger provoquera une copie de version et de lactivit dans tempdb. De mme, ces pseudo-tables nont pas dindex, et les recherches se feront toujours par scan (vous verrez dans le plan dexcution des oprateurs particuliers, avec leur cot relatif : Inserted scan et Deleted scan ). vitez donc les dclencheurs sur des tables massivement modifies. Le plan dexcution du dclencheur est visible via laffichage des plans dans SSMS, que ce soir le plan estim (donc aussi via SET SHOWPLAN_XML) ou le plan rel. Par contre, SET STATISTICS IO ON naffiche pas les statistiques de lectures et dcriture des pseudo-tables. Depuis SQL Server 2005, le ROLLBACK lintrieur dun trigger, pour annuler la transaction de linstruction dclenchante, envoie une erreur 3609 dans la session, afin davertir lutilisateur. Vous pouvez placer le ROLLBACK dans un bloc TRY CATCH pour viter cette erreur.

Bonne pratique En ralit, lerreur 3609 en envoye si @@TRANCOUNT est gal 0 la sortie du dclencheur. Elle apparat donc aussi si un COMMIT TRANSACTION sans BEGIN TRANSACTION est excut dans le trigger, ce qui est sans intrt. Dclencher lerreur 3609 a aussi un sens en cas de ROLLBACK. Il est relativement dangereux dexcuter un ROLLBACK dans un trigger, car cette instruction annule toute la chane des transactions, jusqu la transaction la plus externe. Ce nest peut-tre pas ce que vous souhaitez, dans la mesure o vous navez jamais de garantie que le code dclenchant le trigger ne gre pas de transaction explicite. De mme, un ROLLBACK fermera les ventuels curseurs crs dans le code appelant, sauf sils sont STATIC ou INSENSITIVE et si CURSOR_CLOSE_ON_COMMIT est OFF. Enfin, si vous faites un ROLLBACK, noubliez pas de le faire suivre par un RETURN, pour viter dexcuter ensuite du code qui serait en dehors de la transaction, et donc valid.

8.3 Optimisation du code SQL

283

Il est donc plus prudent deffectuer le ROLLBACK dans le code appelant, ou dutiliser le mcanisme des points de sauvegarde, laide dun SAVE TRANSACTION nom_du_point;, et ensuite dun ROLLBACK TRANSACTION nom_du_point;, qui effectivement nannule que cette partie de la transaction.

Un trigger est dclench quel que soit le nombre de lignes affectes. Une instruction DML avec une clause de filtre qui ne retourne aucun match fera donc excuter le dclencheur. Comme ceci par exemple :
DELETE FROM sales.currency WHERE CurrencyCode = 'FRF'; -- ou plus simplement : DELETE FROM sales.currency WHERE 1 = 0;

Il est bon de tester cela au dbut du trigger, pour viter lexcution du code en pure perte. La variable @@ROWCOUNT est disponible lintrieur du trigger pour cela :
ALTER TRIGGER atr_d$sales_currency2$archive ON sales.currency2 AFTER DELETE AS BEGIN IF @@ROWCOUNT = 0 RETURN; ...

Pour viter dexcuter le code de votre dclencheur si les colonnes qui vous intressent ne sont mme pas mises jour, servez-vous des fonctions UPDATE() et COLUMNS_UPDATED(), qui vous indiquent quelles colonnes ont t mentionnes dans linstruction qui dclenche le trigger. Rfrez-vous au BOL pour la syntaxe de ces fonctions, mais notez quelles nindiquent pas si une colonne est rellement mise jour (si sa valeur a chang), mais si elle est mentionne dans linstruction DML. Un SET NOCOUNT ON est aussi une bonne ide. Un dclencheur, comme tout bon vnement, interagit le moins possible avec la session, et principalement ne renvoie pas de jeu de rsultat. Cest inutile et dangereux pour lapplication cliente, qui peut tre parasite par un recordset indsirable. Vous pouvez dsactiver le renvoi de SELECT malencontreusement prsents lintrieur dun trigger laide de loption de serveur 'disallow results from triggers' :
EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'disallow results from triggers', 1; RECONFIGURE; EXEC sp_configure 'show advanced options', 0; RECONFIGURE;

Si le but est dafficher ou de rediriger le rsultat dune opration, songez utiliser la clause OUTPUT, plus lgre, plutt quun dclencheur : disponible dans les instructions INSERT, UPDATE et DELETE, cette clause rend visible les pseudo-tables DELETED et INSERTED. Exemple pour larchivage :
DELETE FROM sales.currency2 OUTPUT deleted.CurrencyCode, deleted.Name, CURRENT_TIMESTAMP INTO sales.currencyArchive;

284

Chapitre 8. Optimisation du code SQL

Sans la partie INTO, OUTPUT retourne le jeu de rsultat la session. Attention, dans ce cas il ne peut y avoir de dclencheur pour la mme instruction. Nous avons pour linstant trait de dclencheur de type AFTER, cest--dire sexcutant aprs la modification de donnes. Il existe un autre type de dclencheur : INSTEAD OF. Celui-ci sexcute non pas avant linstruction (il nexiste pas de trigger BEFORE en SQL Server), mais la place. Si vous voulez que linstruction qui a dclench le trigger sexcute rellement, vous devez la rcrire dans le corps du trigger, en vous basant sur le contenu des pseudo-tables. Ce type de dclencheur est utile pour rorienter le rsultat dune instruction, ou ne traiter quune partie de celle-ci. Un de ses intrts principaux est quil peut tre plac sur une vue, permettant alors une gestion fine des mises jour travers la vue. Enfin, ajoutons que souvent les dclencheurs sont utiliss pour effectuer des contrles de cohrence. Autant que possible, prfrez les contraintes CHECK aux triggers. Les contraintes CHECK sont trop souvent sous-estimes. Elles peuvent contenir des expressions complexes portant sur toutes les colonnes de la ligne. Si vous devez effectuer des contrles partir de donnes hors de la table, vous pouvez tricher en passant par une fonction utilisateur. Si vous devez faire des vrifications sur un ensemble de lignes de la table, par contre le dclencheur sera probablement plus optimal, de par son comportement ensembliste. La fonction utilisateur, elle, devra tre appele ligne par ligne. Voici, par exemple, un code de trigger permettant de vrifier que des dates de validit dhistorique ne se chevauchent pas (attention, ce code prsuppose que les colonnes fromDate et toDate nacceptent pas les NULL) :
ALTER TRIGGER atr_iu$sales_CurrencyHistory$checkConsistency ON sales.CurrencyHistory FOR INSERT, UPDATE AS BEGIN IF @@ROWCOUNT = 0 RETURN SET NOCOUNT ON IF EXISTS ( SELECT 1 FROM sales.CurrencyHistory ch WITH (READUNCOMMITTED) JOIN inserted i ON ch.CurrencyCode = i.CurrencyCode AND ch.fromDate <> i.fromDate WHERE ( ch.fromDate BETWEEN i.fromDate AND i.toDate OR ch.toDate BETWEEN i.fromDate AND i.toDate ) OR ( i.fromDate BETWEEN ch.fromDate AND ch.toDate OR i.toDate BETWEEN ch.fromDate AND ch.toDate ) ) BEGIN RAISERROR ('Des dates d''historique se chevauchent !', 16, 10) RETURN END END

8.3 Optimisation du code SQL

285

Limiter les pollings avec les notifications de requte Si vous utilisez le client natif SQL (SQL Native Client, ou SNAC - SQLNCLI), vous pouvez profiter de la fonctionnalit de notifications de requtes (Query Notifications). Elle est trs utile pour mettre jour automatiquement les donnes de rfrence dans votre application ou code site web. Souvent, des donnes de rfrences sont charges dans des objets de lapplication client. Ces donnes de rfrences changent rarement, mais pour tre sr dtre jour, le code client effectue des requtes rgulires de vrification, au cas o le contenu de la table aurait chang. Query Notifications est fond sur Service Broker, et permet aux applications de sabonner des vnements qui lui seront envoys par le serveur, travers le client natif. Le code client doit simplement utiliser un objet SqlDependency, sur lequel il place un gestionnaire dvnement. Vous trouverez plus dinformations dans les BOL, sous lentre Using Query Notifications .

9
Optimisation des procdures stockes

Objectif
Il existe deux types de code SQL : la requte labore par le client et envoye au serveur est appele une requte ad hoc, souvent une requte saisie manuellement ou construite dynamiquement, et lobjet de code stock, dont le plus courant est la procdure stocke. La procdure stocke offre de nets avantages de performances. Nous allons expliquer pourquoi, et vous donner les outils pour optimiser vos procdures. La procdure stocke est un objet de code stock sur le serveur, cr laide de la commande CREATE PROCEDURE. Il est hors du domaine de ce livre de vous en prsenter la syntaxe : nous allons simplement nous concentrer sur quelques lments avancs qui vous permettront doptimiser leur utilisation. Autant que possible, prfrez des procdures stockes du code SQL gnr du ct client, mme pour des requtes aussi simples quun unique SELECT. Les procdures stockes permettent dencapsuler et de centraliser du code sur le serveur, qui naura besoin dtre modifi qu un seul endroit en cas de changement de logique ou de structure de donnes. Elles permettent galement de grer la scurit : grce au principe de chanage de propritaire (ownership chaining, voir ce terme dans les BOL), vous pouvez ne donner que les privilges dexcution sur une procdure, sans autoriser laccs aux objets sous-jacents, ce qui permet de contrler prcisment les points dentre et de lecture des donnes. Lappel dune procdure stocke est galement plus concis que lenvoi dun long code SQL, ce qui diminue le trafic rseau.

288

Chapitre 9. Optimisation des procdures stockes

Enfin, la procdure stocke est prcompile et conserve dans un cache de procdures, en mmoire, ce qui vite des recalculs de plans dexcution. Nous dtaillerons cet lment.

9.1 DONE_IN_PROC
Par dfaut, SQL Server retourne un message au client aprs chaque excution dinstruction SQL, indiquant le nombre de lignes affectes. Vous le voyez dans longlet messages de SSMS, lorsque vous excutez la moindre commande. Ce message prend la forme : (14 row(s) affected) (pour une langue de session us_english). Ce message est indpendant des jeux de rsultats eux-mmes, il est envoy dans un paquet TDS travers le rseau chaque excution dune instruction, quelle soit dans un batch ou quelle fasse partie dune procdure stocke. Cela provoque des allers-retours rseau inutiles. Dans la plupart des cas ce message done_in_proc nest pas utilis par le client (la fonction ODBC SQLRowCount() lutilise, mais pas la proprit RecordCount de lobjet ADODB.Recordset). Vous pouvez donc obtenir un gain de performance rel en dsactivant lenvoi des tokens done_in_proc, surtout pendant lexcution de procdures stockes complexes. Pour cela, vous avez plusieurs solutions. La plus simple est de placer, en dbut de vos procdures stockes, linstruction SET NOCOUNT ON, comme ceci :
CREATE PROCEDURE dbo.DoSomethingUseful AS BEGIN SET NOCOUNT ON ...

Ce qui dsactive le renvoi des messages done_in_proc pour le reste de la session. Vous pouvez galement dsactiver globalement ces messages pour toutes les sessions, en modifiant le paramtre de serveur 'user options' :
EXEC sp_configure 'user options', 512 RECONFIGURE

ou laide du drapeau de trace 3640 ajouter la ligne de commande de dmarrage du serveur SQL, dans les paramtres du service (proprit Startup Parameters du service Dans SQL Server Configuration Manager (voir section 7.5.2). Malgr cela, cest une bonne ide dcrire systmatiquement la commande SET NOCOUNT ON au dbut de toutes vos procdures stockes, car loption a pu tre remise OFF dans la session (vous pouvez notamment configurer SSMS pour initialiser cette option louverture de session, voyez la fentre de proprits de la requte, dans la page advanced ). Enfin, vous pouvez tester quel est ltat de cette option de la faon suivante (exemple dutilisation) :
IF @@OPTIONS & 512 = 512 PRINT 'SET NOCOUNT est ON';

9.2 Matrise de la compilation

289

9.2 MATRISE DE LA COMPILATION


Un avantage important de la procdure stocke, est la rutilisation de son plan dexcution. Nous allons dtailler ce mcanisme. Ci-aprs, nous ne parlerons que de procdures, mais gardez lesprit que le mcanisme est le mme pour les fonctions utilisateur (UDF) et pour les dclencheurs, qui sont prcompils de la mme manire. Lorsque la procdure est cre via une instruction CREATE PROCEDURE, son code source est stock dans une table systme de mtadonnes de la base courante. Vous pouvez retrouver ce code grce la vue systme sys.sql_modules :
SELECT definition FROM sys.sql_modules WHERE object_id = OBJECT_ID('dbo.uspGetBillOfMaterials')

Cette requte retourne la dfinition de la procdure nomme dbo.uspGetBillOfMaterials. La vue sys.sql_modules interroge des tables systmes qui ne peuvent plus tre requtes directement depuis SQL Server 2005. Si vous tes curieux de savoir comment ces tables systmes sappellent, vous pouvez retrouver la dfinition de cette vue (comme de tous les objets systme) laide dun de ces commandes :
SELECT OBJECT_DEFINITION(OBJECT_ID('sys.sql_modules'))

ou
SELECT definition FROM sys.system_sql_modules WHERE Object_Id = OBJECT_ID('sys.sql_modules')

Ce stockage nimplique en rien une compilation, ou une optimisation. Il faut distinguer en SQL Server la phase de compilation, proprement parler la vrification des privilges de lutilisateur et de lexistence des objets, de loptimisation. Loptimisation est la gnration dun plan dexcution pour le code SQL. Lorsque la procdure est cre, rien de tout cela nest fait, mme pas la vrification de lexistence des objets : on peut crer une procdure qui rfrence des tables qui nexistent pas, par les vertus de la fonctionnalit de rsolution de nom diffre (Deferred Name Resolution). Cette rsolution diffr ne sapplique quaux tables (et aux vues, qui sont syntaxiquement et conceptuellement identiques aux tables), tout autre objet rfrenc, comme une procdure stocke appele par un EXECUTE, ou une colonne dune table, doivent exister. Ainsi, la cration de la procdure, on peut dire que seule une vrification syntaxique du code SQL est effectue. La vrification de lexistence des tables, comme loptimisation, ne seront ralises que lors de la premire excution de la procdure stocke. Par premire excution , nous entendons premire excution depuis le dmarrage de linstance SQL. Le plan dexcution gnr pour la procdure lors de sa premire excution sera stock en mmoire vive, dans ce quon appelle le cache de plans, ou cache de procdures. Lors des excutions ultrieures de la procdure, ce plan sera utilis, ce qui conomise ltape doptimisation. Le plan reste dans le cache jusquau redmarrage du service. Il est galement possible que, cause dune pression

290

Chapitre 9. Optimisation des procdures stockes

sur la mmoire, le cache se nettoie. Il ne contient au maximum que deux versions dun plan pour une procdure : la version srielle (monoprocesseur) et ventuellement la version paralllise. De plus, ce plan est stock sans information dutilisateur, nous verrons dans la section 9.1.2, ce que cela implique. Le cache de procdure peut tre examin laide dune vue de gestion sys.dm_exec_cached_plans. Allie aux fonctions dynamique : sys.dm_exec_sql_text() et sys.dm_exec_query_plan(), elle vous permet dobserver en dtail ce qui rside dans le cache :
SELECT cp.usecounts, cp.size_in_bytes, st.text, DB_NAME(st.dbid) as db, OBJECT_SCHEMA_NAME(st.objectid, st.dbid) + '.' + OBJECT_NAME(st.objectid, st.dbid) as object, qp.query_plan, cp.cacheobjtype, cp.objtype FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp

Cette requte ne fonctionne qu partir du Service Pack 2 de SQL Server 2005, qui introduit la fonction OBJECT_SCHEMA_NAME(), et amliore la fonction OBJECT_NAME(), lui permettant de passer lidentifiant de base de donnes.

En excutant cette requte, vous constaterez que la colonne cp.objtype contient diffrents types de requtes, et pas seulement des procdures stockes. Nous reviendrons sur la capacit de SQL Server cacher dautres plans dexcution plus loin. Vous pouvez constater en observant cette requte que la vue sys.dm_exec_cached_plans retourne les colonnes usecounts et size_in_bytes, fort utiles pour juger de la taille et de lutilit du cache. Bien entendu usercounts indique le nombre de rutilisation du plan dexcution, et size_in_bytes sa taille en mmoire. Le cache dune procdure complexe peut prendre plusieurs mgaoctets. La taille indique ne dpend dailleurs pas seulement de la complexit du plan dexcution : elle varie aussi selon le nombre dexcutions simultanes de la procdure ou du batch, car, comme nous le verrons, des plans en rapport avec le contexte dexcution sont gnrs lexcution partir de ce plan compil, et eux-mmes cachs. On aura compris une chose : un serveur SQL fortement sollicit, et qui excute des requtes complexes, gagnera fortement avoir le plus de mmoire de travail possible. Larchitecture 64 bits apporte un rel plus dans ce cas de figure. Ds que le plan dexcution de la procdure est en cache, il sera rutilis chaque appel ultrieur, conomisant ainsi le calcul coteux du plan dexcution. Vous pouvez facilement observer les diffrences de temps dexcution entre le premier appel dune procdure et les suivantes avec le profiler, et notamment sur la valeur en temps CPU, qui inclut le temps de compilation. Normalement, la procdure va rester dans le cache. Par contre, comme sur les systmes 32 bits, la mmoire virtuelle est limite, et comme le cache de procdure

9.2 Matrise de la compilation

291

rside dans cette portion de la mmoire (rappelons que la mmoire au-del des 4 Go ne peut tre attribue quau cache de donnes), il peut arriver quune pression sur cette mmoire oblige SQL Server nettoyer le cache1, pour faire de la place pour la mmoire de travail. Dans ce cas, SQL Server va tout de mme sefforcer de conserver les plans les plus coteux recrer. Vous pouvez vous faire une ide du cot dun plan laide des colonnes original_cost et current_cost de sys.dm_os_memory_cache_entries. Voici un exemple de requte :
SELECT DB_NAME(st.dbid) as db, OBJECT_SCHEMA_NAME(st.objectid, st.dbid) + '.' + OBJECT_NAME(st.objectid, st.dbid) as object, cp.objtype, cp.usecounts, cp.size_in_bytes, ce.disk_ios_count, ce.context_switches_count, ce.pages_allocated_count, ce.original_cost, ce.current_cost FROM sys.dm_exec_cached_plans cp JOIN sys.dm_os_memory_cache_entries ce on cp.memory_object_address = ce.memory_object_address CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st

Pression sur le cache de procdures Noubliez pas quun verrou de compilation est pos sur une procdure lors de sa compilation. Il ne peut y avoir quune seule compilation de la mme procdure la fois, ce qui signifie que si la compilation est longue, tous les utilisateurs devront attendre la fin de la compilation. Pour viter ce type de problmes, ncrivez pas de procdures trop longues ou complexes, modularisez au besoin, et assurez-vous de disposer de suffisamment de mmoire de travail (en dessous de la limite des 4 Go en 32 bits).

Si vous souhaitez vider le cache, par exemple pour raliser des tests consistants de performances, vous disposez de la commande DBCC FREEPROCCACHE, qui vide entirement le cache de procdures. Elle est en gnral utilise conjointement avec DBCC DROPCLEANBUFFERS, pour obtenir un tat de la mmoire proche dun dmarrage de linstance SQL.
CHECKPOINT DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE

Il vaut mieux bien entendu viter de lancer ces commandes sur un serveur de production, qui verrait immdiatement ses performances se dgrader jusqu ce que le cache soit de nouveau rempli. Deux autres commandes DBCC, moins connues permettent de vider le cache : pour les plans qui sappliquent une base de donnes spcifique : DBCC FLUSHPROCINDB (DBID) :
SELECT DB_ID('AdventureWorks')
1. travers un memory sweep, lanc par un algorithme de planification, dont on peut voir ltat dans la vue sys.dm_os_memory_cache_clock_hands.

292

Chapitre 9. Optimisation des procdures stockes

DBCC FLUSHPROCINDB (5)

Pour vider plus gnralement les caches de SQL Server : DBCC FREESYSTEMCACHE('ALL').
Plus d'informations sur cette entre de blog : http://sqlblog.com/blogs/ kalen_delaney/archive/2007/09/29/geek-city-clearing-a-single-plan-fromcache.aspx

Il est galement noter que le cache se vide intgralement lors de quelques oprations, quil faut donc viter dexcuter inutilement sur un serveur de production : dtachement dune base de donnes (sp_detach_db) ; utilisation de la commande RECONFIGURE pour appliquer les changements dune option de serveur ; lorsquune vue est cre avec CHECK OPTION, toutes les entres du cache qui rfrencent la base de donnes dans laquelle se trouve la vue, sont vides ; avant SQL Server 2005 Service Pack 2, DBCC CHECKDB vidait le cache. Ce nest plus le cas.

Si vous tes confronts des nettoyages de cache intempestifs, consultez le journal derreur (ERRORLOG) de SQL Server. Depuis le Service Pack 2 de SQL Server 2005, un message dinformation y est enregistr lorsque le cache se vide (aussi lissue dun DBCC FREEPROCACHE). Vous pouvez galement tracer lvnement SQL Trace Errors and Warnings : ErrorLog. Lvnement Security Audit : Audit DBCC Event se dclenche galement lexcution de toute commande DBCC.

9.2.1 Paramtres typiques


Sur quelle base le plan dune procdure est-il calcul ? Lorsque nous passons des paramtres, leurs valeurs peuvent diffrer et gnrer des plans dexcution diffrents. Quen est-il alors du comportement de la procdure stocke ? Malheureusement, il ny a en ce domaine pas de miracle : toutes les instructions de la procdure sont optimises en tenant compte des valeurs de paramtres envoyes. Le premier appel impose donc la qualit du plan dexcution. Si la valeur des paramtres est typique , le plan sera de bonne qualit. Par contre, si des paramtres extrmes sont passs, cela peut produire de mauvaises performances lors des appels ultrieurs. Prenons lexemple de ces deux requtes :
--crons un index pour aider la recherche CREATE NONCLUSTERED INDEX [nix$Person_Contact$LastName] ON [Person].[Contact] (LastName) GO -- 2 lignes retourner

9.2 Matrise de la compilation

293

SELECT FirstName, LastName, EmailAddress FROM Person.Contact WHERE LastName LIKE 'Ackerman' -- 911 lignes retourner SELECT FirstName, LastName, EmailAddress FROM Person.Contact WHERE LastName LIKE 'A%'

lvidence, le plan dexcution sera diffrent : la slectivit de lindex est excellente pour rpondre la premire requte, et SQL Server choisira un seek ; par contre, la deuxime requte sera rsolue par un scan, probablement moins coteux. Vous comprenez dj le problme : si nous crons une procdure stocke de ce type :
CREATE PROCEDURE Person.GetContactByLastName @LastNameStart nvarchar(50) AS BEGIN SET NOCOUNT ON SELECT FirstName, LastName, EmailAddress FROM Person.Contact WHERE LastName LIKE @LastNameStart END

Nous pouvons aisment vrifier que le plan dexcution mis en cache dpend du paramtre envoy lors du premier appel de la procdure. Excutons une premire fois la procdure, et examinons le plan en cache :
EXEC Person.GetContactByLastName 'A%' GO SELECT cp.size_in_bytes, qp.query_plan FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp WHERE st.dbid = DB_ID('Adventureworks') AND st.objectid = OBJECT_ID('Adventureworks.Person.GetContactByLastName')

Dans le plan XML, nous trouvons loprateur de scan dindex clustered, donc de table :
<RelOp NodeId="0" PhysicalOp="Clustered Index Scan" LogicalOp="Clustered Index Scan" EstimateRows="911.466" EstimateIO="0.540903" EstimateCPU="0.0221273" AvgRowSize="126" EstimatedTotalSubtreeCost="0.56303" Parallel="0" EstimateRebinds="0" EstimateRewinds="0">

Si nous appelons la procdure avec le paramtre 'Ackerman', et que nous examinons le plan dexcution dans SSMS, nous constatons que loprateur est toujours un scan : le plan a t rutilis, alors quil nest de loin pas, dans ce cas, le plus efficace par rapport au paramtre envoy. La procdure est compile en utilisant une fonctionnalit appele parameter sniffing (littralement flairage de paramtre ) : le moteur doptimisation dtecte la

294

Chapitre 9. Optimisation des procdures stockes

valeur du paramtre pass. Le plan sera donc calcul selon le paramtre envoy lors du premier appel de la procdure. Si cest 'Ackerman', la procdure fera toujours un seek, si cest 'A%', elle scannera toujours la table. Le parameter sniffing est une bonne chose lorsque le premier appel est pass avec des paramtres reprsentatifs des futurs appels de la procdure. Par contre, cest une mauvaise chose lorsque le premier appel est un cas particulier. Nous allons en faire la dmonstration, ce qui nous permettra de dmontrer une autre particularit du parameter sniffing :
-- procdure avec utilisation directe du paramtre CREATE PROCEDURE dbo.GetContactsParameter @LastName nvarchar(50) = NULL AS BEGIN SET NOCOUNT ON SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE @LastName; END GO -- procdure avec variable locale CREATE PROCEDURE dbo.GetContactsLocalVariable @LastName nvarchar(50) = NULL AS BEGIN SET NOCOUNT ON DECLARE @MyLastName nvarchar(50) SET @MyLastName = @LastName SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE @MyLastName; END GO -- utilisation EXEC dbo.GetContactsParameter @LastName = 'Abercrombie' GO EXEC dbo.GetContactsLocalVariable @LastName = 'Abercrombie' GO EXEC dbo.GetContactsParameter @LastName = '%' GO EXEC dbo.GetContactsLocalVariable @LastName = '%' GO

Avant dexpliquer la raison pour laquelle nous avons cr deux procdures, observons les rsultats de lexcution, prsents figure 9.1. Nous y voyons les statistiques dexcution des quatre appels de procdure, ainsi que le plan dexcution gnr du premier appel.

9.2 Matrise de la compilation

295

Figure 9.1 Recompilations

Nous avons pass dabord le nom 'Abercrombie' : la compilation de la procdure a donc produit un plan bas sur un seek dindex. Ce plan sera rutilis tout au long des excutions futures de la procdure, ce que nous voyons plus loin : un appel avec le paramtre '%' gnre prs de 60 000 reads et le moteur de stockage a t forc de parcourir prs de 20 000 fois lindex, pour trouver lemplacement de chaque ligne de la table. Et quen est-il de la deuxime procdure ? Nous sommes passs par une variable locale, laquelle nous avons affect la valeur du paramtre, puis avons utilis cette variable locale comme oprande de lexpression de filtre. Ce que nous voulions montrer, cest que cette syntaxe ne permet pas le parameter sniffing. Dans ce cas, le plan dexcution gnr prend en compte une distribution moyenne des valeurs dans la colonne, et non pas la valeur actuellement passe la requte, ceci simplement parce que cette valeur nest pas connue lors de loptimisation. Dans notre cas, la distribution moyenne incite SQL Server choisir un scan. Lors du premier appel, cette stratgie est plus coteuse que le seek. Par contre, lors du second appel, le rsultat est bien plus consistant en terme de temps CPU et de reads. Dans le deuxime cas, le scan est la bonne stratgie. Il est donc important que vous preniez ce comportement en compte lorsque vous crez des procdures stockes. Souvent, les paramtres sont assez consistants, et vous navez pas vous soucier du premier plan dexcution gnr, mais dans certains cas, vous devez grer ces diffrences, soit en crivant vos procdures stockes diffremment (en les modularisant, par exemple), soit en les forant se recompiler.

296

Chapitre 9. Optimisation des procdures stockes

Forage de la recompilation
Que faire pour rsoudre le problme des appels de procdures stockes avec des paramtres sappliquant des colonnes dont la distribution est trs variable ? Une solution est de provoquer manuellement la recompilation. Cela peut tre fait de plusieurs faons. Loption WITH RECOMPILE peut tre indique dans le corps de la procdure stocke ou indpendamment chaque appel :
ALTER PROCEDURE Person.GetContactByLastName @LastNameStart nvarchar(50) WITH RECOMPILE AS BEGIN -- ou : EXEC Person.GetContactByLastName 'Ackerman' WITH RECOMPILE

La premire mthode, qui force la recompilation chaque appel, est utile pour une procdure o les valeurs de paramtres sont trs diffrentes chaque fois, la seconde pour forcer la recompilation dans des cas particuliers. Si votre procdure nest appele quavec des paramtres atypiques, prfrez la premire solution. La seconde est dune utilit particulire : un EXEC WITH RECOMPILE gnre un plan dexcution qui ne sera pas mis en cache, et qui ne remplacera donc pas le plan cach existant. Cette mthode sera donc prcieuse si vous avez besoin dappeler ponctuellement une procdure avec un paramtre atypique. La recompilation est une opration coteuse, cest pourquoi il vaut mieux limiter lusage du WITH RECOMPILE des procdures de petite taille, qui en ont vraiment besoin, cest--dire o le cot de la recompilation est moindre que celui de lexcution avec un plan inefficace. Pour les procdures qui comportent de multiples instructions, vous pouvez slectionner une recompilation instruction par instruction, laide de lindicateur de requte OPTION (RECOMPILE). Par exemple :
CREATE PROCEDURE dbo.GetContactsParameter @LastName nvarchar(50) = NULL AS BEGIN SET NOCOUNT ON SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE @LastName OPTION (RECOMPILE); END

Une meilleure solution est de forcer, toujours par indicateur de requte, sur quelle valeur loptimiseur doit se baser :
SELECT FirstName, LastName FROM Person.Contact WHERE LastName LIKE @LastName OPTION OPTIMIZE FOR (@LastName = '%'));

9.2 Matrise de la compilation

297

Linconvnient de cette approche est videmment de coder en dur une valeur de la colonne, qui peut voluer travers le temps, ou disparatre.

En SQL Server 2008, lindicateur OPTIMIZE FOR est complt de la faon suivante : OPTION (OPTIMIZE FOR (@variable = UNKNOWN)) ou OPTION (OPTIMIZE FOR UNKNOWN) qui permettent, comme travers lutilisation de variables locales, de faire une optimisation gnrique partir dune distribution moyenne des valeurs des colonnes, et donc dassurer la stabilit du plan, soit pour une variable, soit pour toutes les variables utilises dans la requte.

Ces options sont trs utiles dans les procdures stockes complexes, o le problme du parameter sniffing se fait le plus aigu, notamment lors de branchements conditionnels. Prenons le cas de cette procdure modulaire :
CREATE PROCEDURE Person.GetContactsByWhatever @NamePartType tinyint, @NamePart nvarchar(50) AS BEGIN SET NOCOUNT ON IF (@NamePartType = 1) SELECT FirstName, LastName, EmailAddress FROM Person.Contact WHERE FirstName LIKE @NamePart ELSE IF (@NamePartType = 2) SELECT FirstName, LastName, EmailAddress FROM Person.Contact WHERE LastName LIKE @NamePart ELSE IF (@NamePartType = 3) SELECT FirstName, LastName, EmailAddress FROM Person.Contact WHERE EmailAddress LIKE @NamePart END

Le but, louable au premier abord, est de crer une procdure gnrique. Malheureusement, la premire valeur envoye dans @NamePart conditionne le plan dexcution des trois instructions, pour trois colonnes diffrentes ! Dans ce cas, la recompilation slective est trs intressante, car elle ne gnrera une compilation que de linstruction utilise (dans laquelle on se branche), au lieu dune recompilation gnrale comme avec un WITH RECOMPILE. Notons tout de mme que la meilleure solution reste dviter la cration de procdures gnriques.

sp_recompile
La procdure stocke systme sp_recompile permet de marquer une procdure ou un dclencheur pour tre recompile. Dans les faits, elle supprime la procdure du cache. Vous pouvez aussi indiquer une table ou une vue. Dans ce cas, toutes les procdures rfrenant cet objet seront recompiles leur prochaine excution. Cette procdure est aujourdhui peu utile, SQL Server faisant ce travail lui-mme,

298

Chapitre 9. Optimisation des procdures stockes

notamment invalidant automatiquement les procdures dun objet qui vient dtre modifi.

9.2.2 Recompilations automatiques


Les recompilations ne sont pas toutes dclenches volontairement. Pour plusieurs raisons, il ne serait pas raisonnable de conserver un plan dexcution intact en cache tout le long de la vie dune instance (qui peut rester active des annes sans tre redmarre). Le serveur SQL vit : non seulement les structures sont susceptibles de changer, ce qui invalide le plan dexcution, mais le contenu des tables volue, entranant des recalculs de statistiques, le contexte dexcution des utilisateurs peut lui aussi changer, etc. Tous ces vnements entranent un changement potentiel de plan dexcution. SQL Server est attentif ces modifications, et peut, lorsque le besoin sen fait sentir, dclencher des recompilations automatiques, lors de lexcution du code. Depuis SQL Server 2005, ces recompilations seffectuent par instruction (statement-level recompilation) et naffectent donc pas la procdure ou le batch tout entier. Elles se produisent pour deux types de raisons : exactitude des donnes la modification des structures sous-jacentes, comme lajout ou la suppression de colonnes de table, de contraintes, la suppression dun index utilis dans le plan, etc. ainsi que la modification doptions de session (notamment lintrieur de la procdure stocke). les options qui peuvent modifier le rsultat des requtes ou la valeur des constantes, comme par exemple SET ANSI_NULLS, SET CONCAT_NULL_YIELDS_NULL, SET DATEFORMAT, etc., peuvent provoquer des recompilations systmatiques. Elles peuvent changer le contexte dexcution, donc forcer le plan dexcution sadapter ce nouvel environnement ; optimisation du plan principalement lors dun recalcul de statistiques. Le plan compil comporte une valeur de seuil de recompilation, et teste chaque excution si le nombre de modifications dans la table dpasse ce seuil. Si cest le cas, la requte est recompile. Les recompilations peuvent savrer coteuses. Elles sont un mal (ou un bien) ncessaire, mais ralentissent parfois inutilement lexcution de procdures stockes. Cela est gnralement d de mauvaises pratiques de programmation, qui dclenchent des recompilations rptitives chaque excution de la procdure stocke, voire plusieurs fois par excution. Nous avons parl des options de sessions. Dautres lments sont considrer : Linterpolation de code DML et de code DDL est susceptible de provoquer des recompilations : la modification de structure dobjets doit tre rpercute dans le plan dexcution des requtes qui les rfrencent ; La mauvaise utilisation de tables temporaires peut entraner des recompilations. La situation depuis SQL Server 2005 est meilleure quen SQL Server 2000 : comme

9.2 Matrise de la compilation

299

les recompilations seffectuent maintenant instruction par instruction, beaucoup de recompilations dues aux tables temporaires ne se produisent plus, car le plan de toute la procdure nest pas invalid et le cache de chaque instruction peut tre rutilis. Lorsque la table temporaire est cre, une recompilation de type compilation dfre (voir plus loin les vnements de trace) est dclenche. Ensuite, linsertion, la mise jour ou la suppression de lignes dans la table temporaire peut rapidement provoquer dautres recompilations, pour prendre en compte les nouvelles cardinalits (la distribution des valeurs dans une colonne). Une recompilation est gnre sur une table temporaire partir de six insertions sur une table vide. Par exemple :
ALTER PROC dbo.testtemptable @nbInserts smallint = 1 AS BEGIN DECLARE @i smallint SET @i = 1 CREATE TABLE #t ( id int identity(1,1) PRIMARY KEY NONCLUSTERED, col char(8000) NOT NULL DEFAULT ('e')) WHILE @i <= @nbInserts BEGIN INSERT INTO #t DEFAULT VALUES SELECT * FROM #t WHERE col = 'e' OR id > 20 SET @i = @i + 1 END END GO EXEC dbo.testtemptable GO

Si vous tracez cette excution avec le profiler, vous verrez un rsultat ressemblant la figure 9.2.

Figure 9.2 Recompilations sur tables temporaires

300

Chapitre 9. Optimisation des procdures stockes

Cest--dire deux recompilations, pour compilation dfre. Si nous excutons ensuite EXEC dbo.testtemptable 7, voici le rsultat en figure 9.3 :

Figure 9.3 Recompilations sur tables temporaires

Nous avons d faire ici une requte SELECT sur la table temporaire un tout petit plus complique que ncessaire, parce que le comportement du cache sur des procdures stockes est bien meilleur quauparavant. La recompilation se dclenche au bout de la sixime insertion dans la table, ce qui est le comportement normal de SQL Server. la deuxime excution de la procdure, il ny aura pas recompilation, linstruction tant dj cache avec un plan valable. SQL Server est donc capable de grer assez bien les recompilations dues aux tables temporaires. Cela ne veut pas dire que vous devez les utiliser sans modration. vitez donc le recours exagr aux tables temporaires. Elles sont souvent inutiles et peuvent tre remplaces par des sousrequtes, des expressions de table (CTE, common table expressions), ou, au pire, des variables de table pour les petits volumes. Si vous tes forc de composer avec des tables temporaires, et que vous dtectez par le profiler des recompilations frquentes dues des changements de cardinalit dans les tables, il vous reste deux options de

9.2 Matrise de la compilation

301

requtes avec lesquelles vous pouvez modifier la frquence des recompilations : KEEP PLAN, et KEEPFIXED PLAN.

KEEP PLAN et KEEPFIXED PLAN


lindicateur de requtes KEEP PLAN modifie les seuils de compilation des tables temporaires (premiers seuils 6 lignes modifies, puis 500 lignes modifies) qui deviennent alors identiques ceux des tables permanentes. Si les modifications apportes des tables temporaires entranent de nombreuses recompilations, essayez de placer cette option dans votre procdure, et vrifiez si cela diminue la frquence de recompilation. Exemple de syntaxe pour notre procdure :
SELECT * FROM #t WHERE col = 'e' OR id > 20 OPTION (KEEP PLAN)

Lindicateur KEEPFIXED PLAN est encore plus contraignant : il empche simplement toute recompilation de linstruction pour raison doptimisation (nouvelles statistiques, changement de cardinalit). Appliquez-le aux requtes que vous avez reconnues comme posant rellement un problme de recompilation.

Tracer les recompilations


Vous pouvez dtecter les recompilations laide dune trace, et des vnements SP:Recompile et SQL:StmtRecompile. Ces vnements vous indiquent, dans la colonne EventSubClass de la trace, pour quelle raison la recompilation a t dclenche. Les valeurs possibles sont celles du tableau 9.1.
Tableau 9.1 Valeurs EventSubClass 1 2 3 Signification La structure de lobjet a chang Les statistiques ont chang Compilation dfre : par exemple, lors de lutilisation dune table temporaire, les instructions de la procdure utilisant cette table ne peuvent tre compiles au dbut de lexcution de la procdure. Elles ne le pourront que lorsque la table sera effectivement cre. Cela dclenchera ce moment une recompilation de ce type. Une option de session a chang Une table temporaire a chang Un jeu de rsultant distant (serveur li) a chang Une permission FOR BROWSE a chang. Lenvironnement de Query Notification a chang Une vue partitionne a chang

4 5 6 7 8 9

302

Chapitre 9. Optimisation des procdures stockes

EventSubClass 10 11 Signification Les options de curseur ont chang La recompilation a t demande par loption de requte RECOMPILE

9.3 CACHE DES REQUTES AD HOC


Le cache contient en ralit bien plus que les plans dexcution des procdures stockes. Il existe trois parties principales du cache (cache stores) qui nous intressent ici et qui stockent des rsultats de compilation : Object Plans (CACHESTORE_OBJCP) : plans de procdures stockes, dclencheurs et fonctions. SQL Plans (CACHESTORE_SQLCP) : plans de batches. Bound Trees (CACHESTORE_PHDR) : arbres danalyse dune requte. Elles comportent chacune une table de hachage qui permet de grer les entres du cache (une hash table compose de hash buckets). Vous pouvez obtenir des informations sur les caches par la vue de gestion dynamique sys.dm_os_memory_cache_counters, et examiner les tables de hachage par la vue sys.dm_os_memory_cache_hash_tables et enfin les hash buckets par sys.dm_os_memory_cache_entries. Il y a galement plusieurs types dobjets dans le cache, les deux qui nous intressent ici sont les plans compils (Compiled Plans, CP) et les plans dexcution (Execution Plans, MXC). La requte suivante vous donne la taille de ces caches :
SELECT Name, Type, single_pages_kb, single_pages_kb / 1024 AS Single_Pages_MB, entries_count FROM sys.dm_os_memory_cache_counters WHERE type in ('CACHESTORE_SQLCP', 'CACHESTORE_OBJCP', 'CACHESTORE_PHDR') ORDER BY single_pages_kb DESC

Les plans compils reprsentent la compilation dune procdure ou dun batch de requtes (cest--dire dordres SQL envoys en un seul lot depuis le client). Sil sagit dune procdure (procdure stocke, fonction, dclencheur), il est stock dans CACHESTORE_OBJCP, sil sagit dun batch, il ira dans CACHESTORE_SQLCP. Les plans dexcution sont en quelque sorte des instances des plans compils, ils sont gnrs rapidement, lexcution, partir dun plan compil. Il y a un plan dexcution par utilisateur lanant la procdure ou le batch. Vous pouvez inspecter

9.3 Cache des requtes ad hoc

303

ces plans laide de la fonction sys.dm_exec_cached_plan_dependent_objects, laquelle vous passez en paramtre un plan_handle venant de sys.dm_exec_cached_plans. Si la procdure est en cours dexcution plusieurs fois simultanment, vous trouverez plusieurs rfrences au mme plan_handle. Les plans compils contiennent un tableau dinstructions SQL. Chaque ordre SQL dans la procdure ou le batch sont compils sparment dans des Cstmt, ou Compiled Statements. Les plans dexcution gnrent des Xstmts, les versions runtime des Cstmt1. Voici une requte pour voir la taille et lutilisation des diffrents objets :
SELECT COUNT(*) as cnt, SUM(size_in_bytes) / 1024 as total_kb, MAX(usecounts) as max_usecounts, AVG(usecounts) as avg_usecounts, CASE GROUPING(cacheobjtype) WHEN 1 THEN 'TOTAL' ELSE cacheobjtype END AS cacheobjtype, CASE GROUPING(objtype) WHEN 1 THEN 'TOTAL' ELSE objtype END AS objtype FROM sys.dm_exec_cached_plans GROUP BY cacheobjtype, objtype WITH ROLLUP

Le code de la procdure ou du batch est stock dans un cache part, le SQL Manager Cache (SQLMGR). Il sagit dun stockage diffrent du plan rellement utilis lexcution. Vous pouvez voir les informations gnrales de ce cache laide de cette requte :
SELECT * FROM sys.dm_os_memory_objects WHERE type = 'MEMOBJ_SQLMGR'

Pourquoi maintenir le code de la requte sparment du plan compil ? Simplement parce que ce plan peut changer, selon ltat de la session, cest--dire le contexte dexcution. Il est important de sy arrter, car cela influe sur la possibilit qua SQL Server de rutiliser un plan en cache.

9.3.1 Rutilisation des plans


Imaginons que nous excutions le mme code, dans deux sessions qui comportent des options de session diffrentes (SET, comme DATEFORMAT, ANSI NULLS), ou dans la mme session, avec un changement doption intermdiaire. Par exemple :
1. Si vous voulez entrer plus en dtail dans lobservation des lments du cache, une srie trs complte darticles a t publie dans le blog de lquipe Microsoft SQL Programmability & API Development : http://blogs.msdn.com/sqlprogrammability/

304

Chapitre 9. Optimisation des procdures stockes

SET CONCAT_NULL_YIELDS_NULL ON GO SELECT TOP 10 FirstName + ' ' + MiddleName + ' ' + LastName FROM Person.Contact GO SET CONCAT_NULL_YIELDS_NULL OFF GO SELECT TOP 10 FirstName + ' ' + MiddleName + ' ' + LastName FROM Person.Contact GO

Ou, imaginons que nous avons deux utilisateurs, Paul et Isabelle, qui sont dclars dans la base avec deux schmas par dfaut diffrents (par exemple Person et HumanResources). Sils excutent tous deux un code identique, qui ne rfrence pas le schma de lobjet, un plan dexcution diffrent doit tre gnr, mme si lobjet qui sera touch est le mme, simplement parce que SQL Server ne sait pas lavance quel va tre le bon objet. En vertu du mcanisme de rsolution de nom, un objet appartenant au schma par dfaut sera dabord recherch, et sil nest pas trouv, SQL Server cherchera un objet appartenant au schma dbo. Exemple :
CREATE TABLE dbo.test (TestId int) GO ALTER USER isabelle WITH DEFAULT_SCHEMA = Person ALTER USER paul WITH DEFAULT_SCHEMA = HumanResources GO CREATE TABLE dbo.test (TestId int) GO GRANT SELECT ON dbo.test TO paul GRANT SELECT ON dbo.test TO isabelle GO EXECUTE AS USER = 'paul' SELECT CURRENT_USER GO SELECT * FROM test GO REVERT GO EXECUTE AS USER = 'isabelle' SELECT CURRENT_USER GO SELECT * FROM test GO REVERT GO

Combien avons-nous de plans ? Vous trouverez sur la figure 9.4 le rsultat de la requte
SELECT st.text, qs.sql_handle, qs.plan_handle FROM sys.dm_exec_query_stats qs

9.3 Cache des requtes ad hoc

305

CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st ORDER BY qs.sql_handle

excute aprs les deux exemples prcdents.

Figure 9.4 Plusieurs plan_handle pour le mme sql_handle

Vous voyez que pour le mme sql_handle, vous avez chaque fois deux plan_handle. Vous pouvez aussi le constater en traant avec le profiler lvnement sp:CacheInsert. Afin de vrifier pour quelle raison (quel attribut du plan) des plans diffrents ont t crs, vous pouvez vous baser sur la vue sys.dm_exec_plan_attributes, comme ceci par exemple (en passant un sql_hande trouv) :
SELECT st.text, qs.sql_handle, qs.plan_handle, pa.attribute, pa.value FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) st OUTER APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa WHERE qs.sql_handle = 0x020000002F5CC820E0CC946DD76094543CF7AA299904C81A and pa.is_cache_key = 1 ORDER BY pa.attribute

Ce quil faut comprendre ici, cest que SQL Server cherche rutiliser autant que possible les plans dexcution en cache, car le recalcul de plans peut tre trs pnalisant. Nous venons de voir que certaines conditions invalident pourtant le plan en cache et obligent SQL Server compiler nouveau le batch ou la procdure. Il faut viter autant que possible de provoquer de telles situations. Les deux cas que nous avons tests prcdemment sont les plus courants : un changement doptions de session en cours de travail modifie le contexte ANSI_NULLS, ANSI_DEFAULTS, dexcution. Des options comme CONCAT_NULL_YIELDS_NULL, ARITHABORT, DATEFIRST, DATEFORMAT, LANGUAGE, QUOTED_IDENTIFIER, etc. peuvent empcher la rutilisation dun plan, parce quelles influent sur les comparaisons, et quelles modifient potentiellement la valeur des littraux exprims dans la requte. SQL Server value trs tt dans la phase de compilation la valeur de ces littraux (une fonctionnalit nomme constant folding). Si une option est modifie, qui peut changer cette valeur dj value, le code doit tre compil nouveau ; lorsquun objet nest pas compltement identifi par son schma, SQL Server ne peut pas garantir que lappel par des utilisateurs dont le schma par dfaut est diffrent, va rfrencer le mme objet, il doit donc recompiler.

306

Chapitre 9. Optimisation des procdures stockes

Deux conseils simposent donc : 1. Maintenez des tats de session consistants en affectant les options la connexion et viter de les changer en cours de route, surtout lintrieur des procdures stockes (le SET NOCOUNT nentre pas dans cette catgorie, il ne provoque aucune recompilation, puisquil ne change pas le comportement des requtes). 2. Prfixez toujours vos objets par leur nom de schma.

En ce qui concerne les options de session, attention aux diffrentes varits de bibliothques client. Les anciennes mthodes de connexion, telles que ODBC, ne placent pas par dfaut les mmes valeurs doption que les mthodes plus modernes, comme ADO.NET. Pour savoir quelles sont les valeurs des options de session, vous pouvez utiliser la commande DBCC USEROPTIONS, ou les vnements Session / ExistingConnection et Security Audit / Audit Login. La diffrence entre le plan compil et le plan dexcution peut tre observe via la vue sys.dm_exec_query_stats, qui rfrence le plan compil dans la colonne sql_handle et le plan dexcution dans la colonne plan_handle. Les handles sont des hachages MD5 gnrs partir du plan entier, ils sont donc garantis uniques par plan. Ils peuvent tre passs la fonction sys.dm_exec_sql_text pour voir le contenu du plan. Lorsque nous avons extrait dans les requtes prcdentes des plans dexcution du cache, laide de la vue sys.dm_exec_cached_plans et de la colonne plan_handle, nous avions accs la totalit du plan compil, et non aux Cstmt individuels. Cette vision tait celle dun cache particulier, vous pouvez en faire lexprience avec cette requte dexemple :
DBCC FREEPROCCACHE GO SET CONCAT_NULL_YIELDS_NULL ON GO SELECT TOP 10 FirstName + ' ' + MiddleName + ' ' + LastName FROM Person.Contact GO SET CONCAT_NULL_YIELDS_NULL OFF GO SELECT TOP 10 FirstName + ' ' + MiddleName + ' ' + LastName FROM Person.Contact GO -- plusieurs plan_handle pour le mme sql_handle SELECT cp.usecounts, cp.size_in_bytes, st.text FROM sys.dm_exec_cached_plans cp OUTER APPLY sys.dm_exec_sql_text (cp.plan_handle) st JOIN sys.dm_exec_query_stats qs ON qs.plan_handle = cp.plan_handle GO

9.3 Cache des requtes ad hoc

307

Cache des requtes ad hoc


Nous lavons vu, les procdures ne sont pas seules tre caches, tout plan dexcution dune requte isole est potentiellement rutilisable. Une premire raction serait de penser que cette fonctionnalit rend la procdure stocke moins intressante, puisque toutes les requtes peuvent profiter dun cache de leur plan dexcution. Ce nest pas vraiment le cas, la procdure reste nettement plus performante, non seulement par les avantages que nous avons dj abords (diminution du trafic rseau, centralisation du code, scurit facilite), mais aussi parce que le cache de requtes est soumis quelques contraintes. Nous allons le voir. Lorsquune requte ad hoc (cest--dire un ordre SQL compos par le client, par opposition du code stock sur le serveur comme une procdure) est envoye au serveur, SQL Server essaie de trouver une correspondance de texte de requte dans le cache SQLMGR. Cette correspondance est recherche en comparant le hachage gnr par la requte entrante avec les hachages prsents dans le cache, puisque ces chanes de hachages reprsentent un rsum de toute la requte cache. Cela permet deffectuer une recherche trs rapide, mais cela signifie aussi que la moindre diffrence de syntaxe invalide la recherche, puisquelle produit un rsultat de hachage diffrent. Une espace de plus, une diffrence de casse dans la requte (mme sur un serveur dont la collation par dfaut est insensible la casse, cela na pas de rapport avec la gestion du cache) suffit gnrer une nouvelle entre dans le cache. Vrifions-le simplement :
DBCC FREEPROCCACHE GO SELECT * FROM dbo.test GO SELECT * FROM dbo.test GO SELECT * FROM dbo.TEST GO select * from dbo.test GO SELECT cp.usecounts, cp.size_in_bytes, st.text FROM sys.dm_exec_cached_plans cp OUTER APPLY sys.dm_exec_sql_text (cp.plan_handle) st JOIN sys.dm_exec_query_stats qs ON qs.plan_handle = cp.plan_handle GO

Le code suivant provoque linsertion de quatre entres de cache, comme nous pouvons le constater dans la figure 9.5.

308

Chapitre 9. Optimisation des procdures stockes

Figure 9.5 Plusieurs plans sur des diffrences syntaxiques

Attention La correspondance avec un plan ad hoc cach nest possible que si les objets sont prfixs par leur schma.

Comme pour le cache de procdure, ces plans dexcution ne sont librs quen cas de besoin mmoire. Mais ce fonctionnement est valable mme en considrant des valeurs constantes diffrentes, passes dans les clauses WHERE pour la recherche ? En dautres termes, des requtes comme :
SELECT * FROM Person.Contact WHERE ContactId = 20 GO SELECT * FROM Person.Contact WHERE ContactId = 40 GO

sont-elles caches sparment ? Cela dpend. Dans cet exemple, un examen de sys.dm_exec_query_stats. Montre ceci :
(@1 tinyint)SELECT * FROM [Person].[Contact] WHERE [ContactId]=@1

Ce qui signifie que SQL Server a transform la requte avant de la mettre dans le cache, pour remplacer les valeurs littrales par un paramtre. Dans certains cas, SQL Server peut donc paramtrer la requte, et transformer la valeur passe comme critre de recherche en paramtre, en interne. Cela permet de rutiliser le plan si dautres paramtres sont passs. Ce mcanisme sappelait paramtrage automatique (auto-parameterization) en SQL Server 2000, et est maintenant nomm paramtrage simple (simple parameterization). Le comportement par dfaut de SQL Server fait quun nombre limit de requtes peuvent tre ainsi paramtres. Par exemple, les deux requtes suivantes sont caches sparment sur SQL Server 2005 sp2 :
SELECT * FROM Person.Contact WHERE LastName = 'Allen' GO SELECT * FROM Person.Contact WHERE LastName = 'Ackerman' GO

Nous avons vu que la valeur de LastName peut ici fortement influencer le plan dexcution, dclenchant soit un seek, soit un scan de lindex. Lexemple prcdent, o le paramtrage avait eu lieu, tait plus facile : ContactId est la cl primaire de la table, donc sa valeur est garantie unique. Une recherche dgalit est donc garantie de retourner une seule ligne, et qui plus est dans une recherche par un index clustered

9.3 Cache des requtes ad hoc

309

(option par dfaut la cration dune cl primaire). Le plan dexcution ne changera pas, quelle que soit la valeur passe en paramtre. SQL Server ne cache donc quun plan quil considre safe , cest--dire qui va pouvoir rpondre pratiquement tous les cas de valeur du paramtre. De plus, il nessaie mme pas de paramtrer des requtes qui comportent des constructions particulires, pour certaines trs courantes, comme des jointures ou des sous-requtes1. Vraiment pas de quoi concurrencer une procdure stocke... Notons que la rutilisation du cache de requtes paramtres est plus souple avec la syntaxe, car elle ne base plus sa recherche sur le mme mcanisme de comparaison de hachage. Une requte paramtre sera alors rationalise pour correspondre la mme requte comportant des espaces ou des retours chariot diffrents. Elle reste toutefois sensible la casse. Pour gnraliser le paramtrage, vous disposez ventuellement dune option de base de donnes qui force SQL Server paramtrer toutes les requtes :
ALTER DATABASE AdventureWorks SET PARAMETERIZATION FORCED -- pour revenir l'tat par dfaut : ALTER DATABASE AdventureWorks SET PARAMETERIZATION SIMPLE

En passant en paramtrage forc (Forced Parameterization), toutes les valeurs littrales rencontres dans une requte sont converties en paramtres. La requte prcdente sera alors stocke dans le cache avec la syntaxe suivante :
(@0 varchar(8000))select * from Person.Contact where LastName = @0

Apparemment utile, cette option nest pas activer la lgre, car le paramtrage implique un effort plus important de SQL Server, aussi bien la gnration du plan dexcution qu la recherche de correspondances dans le cache. Elle peut aussi provoquer la conservation et la rutilisation de plans non optimaux, puisque les limites du paramtrage simple servent justement gnrer des plans diffrents lorsque cest ncessaire. Elle nest donc pas la panace. Rien ne remplace la simplicit dune procdure stocke.

Vous noterez au passage que le paramtre gnr est de type varchar(8000), alors que la colonne recherche est un nvarchar(50). Le type de la colonne nest pas vrifi par SQL Server, qui applique le type de la constante, et la taille maximum du type de donnes. Cette opration est appele bucketization. Elle est force par le serveur dans ce cas, mais vous avez la possibilit de paramtrer votre requte dans votre code client (par exemple en utilisant la collection Parameters de lobjet SQLCommand en ADO.NET avec des types de donnes explicites), et ainsi doptimiser le type de donnes du paramtre, pour une commande que vous allez rutiliser plusieurs fois.
1. Vous en trouvez une liste non exhaustive dans cette entre de blog : http://blogs.msdn.com/ sqlprogrammability/archive/2007/01/11/4-0-query-parameterization.aspx

310

Chapitre 9. Optimisation des procdures stockes

SQL Server 2008 intgre une option de serveur nomme optimize for ad hoc workloads , qui vous permet de diminuer la consommation de mmoire en cache de plan, lorsque votre serveur est principalement utilis avec des requtes dynamiques, non paramtres. Pour lactiver, excutez ce code :

EXEC sp_configure 'show advanced options',1 RECONFIGURE EXEC sp_configure 'optimize for ad hoc workloads',1 RECONFIGURE

9.3.2 Paramtrage du SQL dynamique


Une facilit est offerte pour lexcution paramtre du SQL dynamique. Au lieu dutiliser la commande EXECUTE(), qui se contente dvaluer et denvoyer linstruction SQL telle quelle, la procdure stockes systme sp_executesql permet de dclarer des paramtres dans la chane et de les envoyer chaque appel, paramtrant ainsi explicitement le plan dexcution. Voici un exemple qui prsente les deux solutions :
DBCC FREEPROCCACHE GO DECLARE @sql varchar(8000) SET @sql = 'SELECT * FROM Person.Contact WHERE LastName = ''Allen''' EXECUTE (@sql) SET @sql = 'SELECT * FROM Person.Contact WHERE LastName = ''Ackerman''' EXECUTE (@sql) GO DECLARE @sql nvarchar(4000) SET @sql = 'SELECT * FROM Person.Contact WHERE LastName = @LastName' EXECUTE sp_executesql @sql, N'@LastName NVARCHAR(80)', @LastName = 'Allen' EXECUTE sp_executesql @sql, N'@LastName NVARCHAR(80)', @LastName = 'Ackerman' GO SELECT cp.usecounts, cp.size_in_bytes, st.text FROM sys.dm_exec_cached_plans cp OUTER APPLY sys.dm_exec_sql_text (cp.plan_handle) st JOIN sys.dm_exec_query_stats qs ON qs.plan_handle = cp.plan_handle GO

Bien sr, autant que possible, prfrez la syntaxe avec sp_executesql. Il est noter quune fonctionnalit similaire est propose par les bibliothques daccs client, qui permettent de prparer le code SQL envoyer, pour favoriser la rutilisation du plan dexcution. OLEDB expose linterface IcommandPrepare pour ce faire. Nutilisez pas systmatiquement cette fonctionnalit, elle est souvent

9.4 propos du code .NET

311

plus coteuse quutile. Rservez-la des excutions multiples au moins plus de trois fois de la mme requte.

9.4 PROPOS DU CODE .NET


Depuis SQL Server 2005, vous pouvez crire des objets de code SQL Server dans un langage .NET, compil en langage intermdiaire (MSIL). Aprs avoir ajout lassembly compile dans les mtadonnes de votre base de donnes ( laide de la commande CREATE ASSEMBLY), vous pouvez utiliser les classes et mthodes de votre assembly pour crer des procdures stockes, dclencheurs, fonctions utilisateur, types de donnes ou fonctions dagrgation. la sortie de SQL Server 2005, certaines personnes se posaient la question de passer du langage T-SQL vers .NET pour la plupart de leurs besoins en requte, les dveloppeurs ayant souvent une connaissance plus pousse de C# ou VB.NET que du langage SQL. Cette question, bien sr, na pas eu de suite. Il est inutile, et dailleurs impossible, de vouloir remplacer T-SQL. SQL Server est un SGBDR qui respecte autant que possible les rgles de Codd1, et les principes du client-serveur : les donnes ne sont accessibles qu travers le langage de requte, ce langage tant SQL. Ainsi, mme dans du code .NET, laccs au contenu des tables ne peut tre fait qu travers une connexion ouverte sur le serveur (une mthode de connexion in-process rapide est disponible, appele la connexion de contexte ), et le passage de commandes T-SQL. Pour manipuler les objets de base, le code .NET in-process dispose de... ADO.NET 2.0. Il ny a donc aucun moyen de remplacer la saisie de code SQL. Cette contrainte volue avec SQL Server 2008, dans lequel il est thoriquement possible de crer des objets .NET qui utilisent la technologie de mapping relationnel-objet nomme LINQ (.NET Language Integrated Query). Il devient ainsi possible de se passer de saisir du code SQL, les bibliothques .NET soccupant pour vous de traduire votre prose objet en requtes. Il est clair que cette voie est en opposition avec le sujet de ce livre. Si vous tes soucieux de performances, fuyez tout jamais cette voie. Utilisez la technologie bon escient. Le langage de requtes natif de SQL Server, Transact SQL, doit rester la solution de choix pour toute requte. ventuellement, comme SQL est trs peu souple en ce qui concerne les traitements en boucle et lalgorithmique, et comme le code procdural est en gnral plus lent en T-SQL que dans un langage compil, dans des cas comme la manipulation de chanes de caractres ou des oprations mathmatiques, lintgration du CLR peut savouer un alli utile. Nous parlons videmment ici de la tentation de dplacer du code sexcutant
1. Edgar F. Codd a publi en 1985 dans le magazine ComputerWorld deux articles rests clbres, qui listent treize rgles, ou lois, essentielles dun systme de bases de donnes relationnelles. La rgle n 12, notamment, connue sous le nom de rgle de non-subversion, stipule que si le systme dispose dun langage de bas niveau, ce langage ne peut pas contourner ou remettre en cause les contraintes de scurit et les rgles dintgrit nonces au plus haut niveau. Voir http://www.sqlspot.com/Les-regles-de-CODD-pour-un-SGBD-relationnel.html.

312

Chapitre 9. Optimisation des procdures stockes

traditionnellement dans une couche mtier, dans le moteur de base de donnes, ce nest pas forcment non plus une bonne ide. Dun ct vous gagnez en diminution de transfert rseau entre la couche de donnes et la couche mtier, dun autre, vous grignotez avidement les ressources dune machine ddie normalement la manipulation de donnes brutes. Ne vous y lancez quen cas de relle ncessit.

SQL Server

Bibliographie

Ouvrages utiles
Itzik BEN-GAN, Inside Microsoft SQL Server 2005 : T-SQL Querying, Microsoft Press, 2006. William BOSWELL, Inside Windows Server 2003, Addison Wesley, 2003. Louis DAVIDSON, Kurt WINDISCH, et Kevin KLINE, Pro SQL Server 2005 Database Design and Optimization, Apress, 2006. Kalen DELANEY, Inside Microsoft SQL Server 2005 : Query Tuning and Optimization, Microsoft Press, 2007. Kalen DELANEY, Inside Microsoft SQL Server 2005 : The Storage Engine, Microsoft Press, 2006. Edward L. HALETKY, VMware ESX Server in the Enterprise : Planning and Securing Virtualization Servers, Prentice Hall PTR, 2008. Ken HENDERSON, SQL Server 2005 Practical Troubleshooting : The Database Engine, Addison-Wesley Professional, 2006. Ken HENDERSON, The Gurus Guide to SQL Server Architecture et Internals, AddisonWesley Professional, 2004. (traite de SQL Server 2000, mais de nombreux passages sont utiles la comprhension du fonctionnement de SQL Server 2005 et 2008). Ralph KIMBALL et Margy ROSS, Entrepts de donnes. Guide pratique de modlisation dimensionnelle, 2e dition, Vuibert Informatique, 2002. Steven WORT, Christian BOLTON, et Justin LANGFORD, Professional SQL Server 2005 Performance Tuning, Wrox Press, 2008.

314

Optimiser SQL Server

Papiers techniques et livres blancs Microsoft


( chercher sur le site de Microsoft, les liens tant parfois changs) Database Engine Tuning Advisor (DTA) in SQL Server 2005 document Word.

Rfrences web
SQL Server Query Optimization Team, Distributed/Heterogeneous Query Processing in Microsoft SQL Server, http://citeseer.ist.psu.edu/732761.html Physical Database Storage Design - http://www.microsoft.com/technet/prodtechnol/sql/2005/physdbstor.mspx Travaux de recherches sur lesquels est bas le Database Engine Tuning Advisor http://research.microsoft.com/research/dmx/autoadmin/ SQL Server I/O Basics, Chapter 2 http://www.microsoft.com/technet/prodtechnol/sql/2005/iobasics.mspx Working with tempdb in SQL Server 2005 http://www.microsoft.com/technet/ prodtechnol/sql/2005/workingwithtempdb.mspx

Sites
GUSS : http://www.guss.fr/ developpez.com : http://sqlserver.developpez.com/ forums developpez.com : http://www.developpez.net/forums/ SQL Server Performance http://www.sql-server-performance.com/ SQL Server Central http://www.sqlservercentral.com/ SQL Server Magazine (payant) http://www.sqlmag.com/ Site Microsoft Technet http://technet.microsoft.com/fr-fr/sqlserver/ SQL Team http://www.sqlteam.com/ SQL Server Community http://www.sqlcommunity.com/ Simple Talk (site communautaire de RedGate, un diteur doutils tiers pour SQL Server) http://www.simple-talk.com/sql/ Newsgroups Microsoft (USENET) : microsoft.public.fr.sqlserver, microsoft.public.sqlserver.programming. Cherchables via Google Groups : http:// groups.google.com/

Blogs
SQL Server Storage Engine http://blogs.msdn.com/sqlserverstorageengine/ SQL Server Manageability Team Blog http://blogs.msdn.com/sqlrem/ Microsoft SQL Server Development Customer Advisory Team http:// blogs.msdn.com/sqlcat/ Microsoft SQL Server Support Blog http://blogs.msdn.com/sqlblog/ SQL Programmability & API Development Team Blog http:// blogs.msdn.com/sqlprogrammability/

Bibliographie

315

SQL Protocols http://blogs.msdn.com/sql_protocols/ Solid Quality Learning Weblogs http://solidqualitylearning.com/blogs/ Blogs by Current and Former Microsoft Most Valuable Professionals http:// msmvps.com/tags/SQL+Server/default.aspx SQL Server Community Blogs http://sqlblogcasts.com/blogs/ Tibor Karaszi http://www.karaszi.com/SQLServer/ SQLJunkies Weblogs http://www.sqljunkies.com/WebLog/ Australian SQL Server User Group Blogs http://blogs.sqlserver.org.au/blogs/ SQL Skills team blogs http://www.sqlskills.com/blogs.asp Christian Robert, MVP http://blogs.developpeur.org/christian/ Brian Knight http://www.whiteknighttechnology.com/cs/blogs/ Liste des blogs des quipes SQL Server de Microsoft http://blogs.msdn.com/ sqlserverstorageengine/archive/2007/02/14/wow-lots-of-blogs-from-the-sqlproduct-team.aspx sqlblog.com http://sqlblog.com/blogs

Index

Index

Symboles
.NET 311 @@ROWCOUNT 283 @@SPID 106

C
cache 22, 36 cardinalit 47, 48 CHECKPOINT 36 checkpoint 16 cl 47 candidate 48 trangre 48 intelligente 48 naturelle 48 primaire 47 recherche de 159 rles 49 technique 48 client-serveur 8 compteur de performances 29, 39, 226 context switch 22

A
alertes 133 APP_NAME() 105 arbre quilibr 148 architecture client-serveur 8 SMP 33

B
baseline 6 BIDS 33 blocage 230 bookmark lookup 148 buffer cache hit ratio 34 manager 36 pool 36 Business Intelligence 20

D
data marts 56 data warehouses 56 Database Engine Tuning Advisor 196 DATALENGTH() 64 DBCC DBCC DROPCLEANBUFFERS 36

318

Optimiser SQL Server

DBCC FLUSHPROCINDB 291 DBCC FREEPROCCACHE 291 DBCC FREESYSTEMCACHE 292 DBCC IND 71, 154, 173 DBCC PAGE 12, 157, 173 DBCC PSS 227 DBCC SHOW_STATISTICS 188, 193 DBCC USEROPTIONS 226 deadlock 230, 235 dclencheur 50, 281 dnormaliser 55 dpassement de ligne 70 dimensions 57 disque pression 39 drapeau de trace 1118 86 1204 237 1211 218 1222 237 3640 288

gouverneur de requtes 88 de ressources 89 granularit de verrouillage 214 groupe de fichiers 10 GUID 68 guide de plan 260

H
heap 175 HOST_NAME() 106 hyper-threading 28

I
IAM 12, 14 IDENTITY 48, 62 index 147, 265 ALLOW_PAGE_LOCKS 170 ALLOW_ROW_LOCKS 170 B-Tree 148 clustered 68, 150, 159 composite 170 couverture 161 cration 164 densit 187 DROP_EXISTING 169 FILLFACTOR 165 filtr 163 inclusion 162 manquant 180 MAXDOP 170 ONLINE 169 profondeur 159 REBUILD 167 REORGANIZE 167 scan 148 slectivit 187 SORT_IN_TEMPDB 169 statistiques 185 string summary 192 taille 171 indexation 147 instance 9

E
vnement notification 231 exceptions 106 extension 9, 11

F
fichier de donnes 9 groupe de 10 taille 20 virtuel de journal 20 FILLFACTOR 121 fn_dblog 18 fonctions utilisateur 267 formes normales 51 forwarding record 180 fragmentation externe 166 interne 165

J G
GAM 12 jointure boucle imbrique 254

Index

319

fusion 56, 255 hachage 255 journal derreur 13, 28 de transactions 15 de transactions, VLF 20

N
normalisation 49 NUMA 28, 33 Soft-NUMA 34

O
OLAP 56 OLTP 56 optimisation base sur le cot 184

K
KEEP PLAN 301 KEEPFIXED PLAN 301

L
latches 213 ligne 11 LIKE 63 Log Parser 140 LSN 18

P
page 11 affichage du contenu 12 de donnes 10 IAM 14 paralllisme 26 paramtrage forc 309 partitionnement 77, 80 plan de 81 schma 81 PFS 14 PHP 182 plan dexcution 241 bookmark lookup 192 hachages 255 Key Lookup 156 nested loop 149 paralllis 246 point de contrle 16 de reprise 15 procdure stocke OPTIMIZE FOR 296 parameter sniffing 293 Process Explorer 29 processeur 26 profiler 97, 239, 248, 262 colonnes 102 vnements 101 filtres 102

M
MAXDOP 28 mmoire 3GB 30 AWE 31, 38 cache de donnes 36 PAE 31 vive 30, 35 mesures 57 MMU 31 mode de rcupration 17 modle conceptuel de donnes 46 en toile 57 en flocon 58 OLAP 56 physique de donnes 46 relationnel 46 moniteur de performance 113 systme 113 moniteur systme journal de compteurs 130, 134 moteur de requte 8 de stockage 8 relationnel 8

R
RAID 39 RAM 30 recovery 16 recovery interval 16 rcupration 16

320

Optimiser SQL Server

rcursivit 278 refactoring 55 rgles mtier 50 requte ad hoc 307 RID 148, 159 lookup 148 rollback 15 row overflow 71 row versioning 85

S
SARG 265 scan 148 scheduler 21 seek 158 Server Performance Advisor 143 serveur de test 205 SET ANSI_PADDING 63 SET SHOWPLAN... 248 SET SHOWPLAN_TEXT ON 173 SET STATISTICS IO ON 74, 95, 104 SET STATISTICS TIME ON 94 SGAM 12 SGBDR (systme de gestion de base de donnes relationnelles) 7 sp_configure user options 288 sp_configure 16 affinity mask 27 blocked process threshold 232 disallow results from triggers 283 max degree of parallelism 27, 28 max server memory 38, 121 max worker threads 126 optimize for ad hoc workloads 310 sp_executesql 310 sp_recompile 297 Sparse columns 15 SPF 12 spilling 255 SQL code 241, 262 dynamique 272, 310 SQL Server architecture 7 configuration du serveur 42 dtection d'erreurs 106 mmoire vive 35 page 11

SQLOS 20 stockage 9 SQL Server 2005 dition Entreprise 80 ramp-up 39 SQL Server 2008 23 vnements tendus 136 filtre d'index 163 gouverneur de ressources 89 Management data Warehouse 143 OPTIMIZE FOR 297 Sparse columns 15 verrouillage 218 SQL Trace 96, 111 SQL trace impact 111 sqlcmd.exe 111 SQLDIAG 139 SQLNCLI 285 SQLNexus 140 SQLOS 9, 20 worker 22 SSMS 93 statistique du client 93 statistique 183 Auto Stats 193 AUTO UPDATE STATISTICS 195 AUTO_UPDATE_STATISTICS_ASYNC 196 consultation 187, 193 CREATE STATISTICS 193 de temps 95 densit 187 mise jour 194 slectivit 187 sp_autostats 195 sp_updatestats 195 statistiques index 185 sys.allocation_units 192 Sysinternals 140

T
table temporaire 298 TDS 8 tempdb 39, 85, 127, 169, 270 emplacement 88 taille 86 test de charge 110

Index

321

trace 107, 134 par dfaut 112 rejouer 108 transaction 207 ACID 208 dirty read 219 explicite 209 implicite 209 isolation 209 isolation READ COMMITTED 221 isolation READ UNCOMMITTED 220 isolation SERIALIZABLE 222 isolation SNAPSHOT 224 lost update 219 niveau disolation 219 non-repeatable read 219 phantom read 219 REPEATABLE READ 221 type de donnes 59 chanes de caractres 62 DATETIME 65 DATETIME2 66 FILESTREAM 74 LOB 69 numriques 59 SMALLDATETIME 65 sql_variant 67 temporelles 65 TIMESTAMP 65 UNICODE 63 UNIQUEIDENTIFIER 68 VARCHAR(MAX) 69 XML 66

U
UDF 267 uniquifier 158

verrous intentionnels 212 verrous partags 210 version stores 85 virtualisation 40 VLDB 81 VLF (Virtual Log Files) 122 VMware ESX 41 vue de gestion dynamique 15 dm_exec_query_plan 250 sys.dm_db_file_space_usage 87 sys.dm_db_index_operational_stats 121 sys.dm_db_index_operational_stats 121, 177, 215, 218 sys.dm_db_index_physical_stats 119, 167 sys.dm_db_index_usage_stats 159, 176 sys.dm_db_session_space_usage 87 sys.dm_db_task_space_usage 88 sys.dm_exec_cached_plans 290 sys.dm_exec_requests 230 sys.dm_exec_sessions 227 sys.dm_exec_sql_text 250 sys.dm_os_buffer_descriptors 15 sys.dm_os_memory_cache_counters 302 sys.dm_os_memory_cache_entries 291 sys.dm_os_performance_counters 129 sys.dm_os_sys_info 31 sys.dm_os_wait_stats 228 sys.dm_os_waiting_tasks 86, 213, 230 sys.dm_tran_active_snapshot_database_ transactions 87, 225 sys.dm_tran_locks 221 sys.dm_tran_version_store 225 vue indexe 181

W V
Windows Performance Toolkit 139 WITH RECOMPILE 296 WOW 33

verrouillage 209 granularit 215 index 215 verrous dtendue 213 verrous de mise jour 211 verrous de schma 213 verrous exclusifs 211

X
XML blob 67 type de donnes 66 Xperf 139

INFOPRO

TYPE DOUVRAGE L'ESSENTIEL SE FORMER RETOURS D'EXPRIENCE

Rudi Bruchez

MANAGEMENT DES SYSTMES D'INFORMATION APPLICATIONS MTIERS TUDES, DVELOPPEMENT, INTGRATION

OPTIMISER SQL SERVER


Dimensionnement, supervision, performances du moteur et du code SQL
Cet ouvrage sadresse aux dveloppeurs, administrateurs de bases de donnes (DBA), consultants et professionnels IT qui ont la responsabilit dune base SQL Server. Un systme comme SQL Server joue un rle central dans linformatique dune entreprise, et il est indispensable de sassurer que lon en obtient les meilleures performances possibles, surtout sur des volumes importants. Cet ouvrage va vous aider tirer le maximum de SQL Server dans ses versions 2005 et 2008. En comprenant larchitecture et le fonctionnement du moteur de ce gestionnaire de base de donnes, vous saurez comment choisir votre matriel et surveiller votre serveur. Vous apprendrez interprter les rsultats des compteurs de performances, et utiliser les traces SQL. Vous saurez comment utiliser les index de faon optimale. Vous comprendrez les problmatiques de qualit du modle de donnes et du code SQL. Vous matriserez en dtail le mcanisme des transactions, le verrouillage, les blocages et les verrous mortels qui peuvent sensuivre. Vous apprendrez enfin optimiser vos procdures stockes.
Retrouvez sur www.babaluga.com et sur www.dunod.com le code source des exemples de louvrage

EXPLOITATION ET ADMINISTRATION RSEAUX & TLCOMS

RUDI BRUCHEZ est consultant et formateur spcialis sur SQL Server depuis 2001. Certifi Microsoft (MCDBA, MCT et MCITP) et MVP (Most Valuable Professional) sur SQL Server, il est aussi rdacteur/modrateur sur le forum SQL Server du site communautaire developpez.com, principal site francophone pour les dveloppeurs. Il rpond galement rgulirement aux questions poses sur le newsgroup officiel de Microsoft consacr SQL Server (microsoft.public.fr.sqlserver).

ISBN 978-2-10-053750-1

www.dunod.com