Inside SQL Server, Microsoft Press.
Traduction personnelle. But pédagogique.


Le moteur SQL Server

La figure 3-1 montre l'architecture générale de SQL Server. 

Regardons maintenant avec détail les modules principaux.

La Net-Library

La Net-Library (souvent appelée ) est la couche d'abstraction qui permet de d'envoyer et d'acquérir des données vers SQL Server en utilisant différents protocoles réseau, et chaque protocole (comme les sockets TCP/IP) peuvent avoir un pilote spécifique. La couche Net-Library permet de supporter différents protocoles réseau sans avoir à changer le code principal du serveur.

Un pilote Net-Library est spécifique à un mécanisme réseau de communication inter-processus (IPC). Tout le code de SQL Server, Net-Library inclue, fait uniquement appel au sous système Microsoft Win32. SQL Server utilise une interface interne commune entre ODS (Microsoft Open Data Services), qui gère l'utilisation du réseau, et chaque Net-Library. Il est possible de charger plusieurs Net-Libraries simultanément, une pour chaque mécanisme IPC utilisé.

SQL Server utilise la couche d'abstraction Net-Library sur les machines cliente et serveur, rendant ainsi possible le support de clients en simultané sur différents réseaux. Microsoft Windows NT, Windows 95, et Windows 98 autorisent l'utilisation d'une pile multi-protocoles en simultané sur un système. Les Net-Libraries doivent être couplées. Par exemple, si une application client utilise une Net-Library à base de pipes nommés, SQL Server doit être capable d'écouter sur une Net-Library à base de pipes nommés. La gestion de l'utilisation des Net-Library se fait par l'outil de configuration réseau. (SQL Server Network Utility sous Programs\Microsoft SQL Server 7.0)

Figure 3-1. Les composants principaux de l'architecture SQL Server

Il est important de distinguer les mécanismes IPC des protocoles réseaux sous-jacents. Les mécanismes IPC utilisés par SQL Server sont pipes nommés, RPC, SPX, et Windows Sockets. Les protocoles réseaux utilisés sont TCP/IP, NetBEUI, NWLink (IPX/SPX), Banyan VINES SPP, et Appletalk ADSP. Deux Net-Library, Multiprotocol et Named Pipes, peuvent être utilisées en simultané au-dessus de plusieurs protocoles réseaux (NetBEUI, NWLink IPX/SPX, and TCP/IP). Vous pouvez avoir plusieurs protocoles réseaux dans votre environnement et n'utiliser qu'une seule Net-Library.

Sur les machines avec Windows 95 ou Windows 98, vous pouvez utiliser Shared Memory comme mécanisme IPC. Il joue le rôle d'IPC et de protocole réseau et fonctionne seulement pour les connexions locales dans le cas ou le client s'exécute sur la même machine que SQL Server. De plus, SQL Server ne supporte pas les pipes nommés comme IPC sur les machines Windows 95 ou Windows 98.

La Net-Library Multiprotocol utilise les services RPC de Windows NT, Windows 95, ou Windows 98. Elle pourrait se nommer "RPC Net-Library", mais "Multiprotocol" symbolise mieux son usage. La Net-Library Multiprotocol utilisant les services RPC du système d'exploitation, elle crypte tout le trafic (incluant les requêtes, les données et les mots de passe) entre l'application client et le moteur SQL Server.

Les pipes nommés et les services RPC de Windows NT supportent l'impersonnalisation des contextes de sécurité pour permettre une authentification intégrée (connue aussi sous le nom de l'authentification Windows NT). Au lieu de demander un identifiant d'utilisateur et un mot de passe à chaque connexion à SQL Server, SQL Server impersonnalise le contexte de sécurité de l'utilisateur utilisant l'application qui demande la connexion. Si l'utilisateur a des privilèges suffisants (ou qu'il fait parti d'un groupe appartenant à un domaine Windows NT), la connexion est établie. L'authentification Windows NT n'existe pas sur les machines exécutant SQL Server sous Windows 95 ou Windows 98. Dans ce cas, la connexion se fait en fournissant un identifiant d'utilisateur et un mot de passe SQL Server.

Open Data Services

Open Data Services (ODS) gère les communications entre le client et SQL Server; c'est une interface entre les serveurs Net-Libraries et les applications serveurs, comme SQL Server. ODS gère les connexions, le protocole applicatif (ACK), les commandes et coordonne les services de thread, et retourne les jeux de résultats, les codes et les messages au client.

Les clients SQL Server et le serveur utilisent un protocole privé nommé TDS (tabular data stream). TDS est un flux de données auto descriptif. En d'autres termes, il contient des balises de description des noms de colonnes, des types de données, des événements (comme les annulations), et les codes d'état dans la conversation entre le client et le serveur. Le serveur notifie le client de l'envoie d'un jeu de données, indiquant le nombre de colonnes et leurs types de données. Aucun client ou serveur ne s'adresse directement à TDS. En fait, le client utilise des interfaces de programmation comme DB-Library et ODBC qui émettent du TDS. Tous deux utilisent une implémentation client de la Net-Library.

ODS accepte les nouvelles connexions, et si un client se déconnecte prématurément (par exemple, lors d'un redémarrage de machine sans fermeture de l'application client), les ressources comme les verrous qui sont conservées par le client sont libérés automatiquement.

Vous pouvez utiliser l'interface ODS pour écrire une application serveur, comme une passerelle. Ce sont des applications serveurs ODS. SQL Server est une application serveur ODS, et utilise la même DLL (OPENDS70.DLL) que les autres applications ODS.

Lecture et Ecriture de buffers ODS

SQL Server poste les jeux de résultats sur le buffer de sorti réseau qui est égal en taille à la taille de packet configurée, et la Net-Library envoie le buffer au client. Le premier packet est envoyé aussi tôt que le buffer de sorti réseau (le buffer d'écriture) est plein ou, si un jeu de résultat entier tient dans un packet, lorsque le traitement est terminé. Dans certaines opérations exceptionnelles (comme celles qui donnent un état d'avancement dans le transfert d'une base ou celles qui fournissent des messages DBCC), le buffer de sorti est même envoyé avant même qu'il soit plein ou avant que le traitement se termine.

SQL Server a deux buffers en entrée (buffers de lecture) et un buffer de sorti par client. Deux buffers de lecture sont nécessaires car lorsque SQL Server effectue une opération de lecture de données sur une connexion client, il doit aussi être capable d'être à l'écoute d'une demande d'annulation d'une requête.

Du fait que le système d'exploitation fourni un cache pour les écritures réseaux, les opérations en écriture semblent se terminer immédiatement. Mais si plusieurs écritures sont issues d'un même client et que le client n'est pas en phase de lecture de données en provenance du réseau, le cache réseau devient éventuellement plein et écrit les blocks. C'est un blocage. Aussi longtemps que l'application client traite le jeu de résultat, SQL Server a peu de buffer rempli et prêt pour traiter les connexions client. Mais si la file d'attente du client  est déjà remplie par des données et n'est pas traitée, SQL Server est en attente pour envoyer les données et les opérations d'écritures réseau sur cette connexion sont mise en attente. Tant que le serveur n'a qu'un seul buffer de sortie par client, les données ne peuvent être envoyées au client tant qu'il ne récupère les données réseaux pour permettre la fin des opérations d'écriture.

SQL Server ajoute les enregistrements au buffer de sorti lorsqu'il les récupère. Souvent, SQL Server continue d'acquérir des enregistrements qui satisfont les critères de recherche pendant que les enregistrements déjà récupérés sont envoyés au client.

Les écritures réseaux mises en attente affectent aussi les verrous. Par exemple, si le mode d'isolation READ COMMITTED (par défaut) est appliqué, un verrou partagé peu normalement être libéré après que SQL Server a terminé son scan de la page de données. (Les verrous exclusifs de mise à jour de données doivent toujours être conservés jusqu'à la fin de la transaction pour permettre que les changements soit annulés.) Cependant, si le scan trouve plus de données et que le buffer de sorti n'est pas vide, le scan est mis en attente. Lorsque l'écriture réseau précédente se termine, le buffer de sortie devient accessible et le scan reprend. Mais l'écriture ne se terminera pas tant que la connexion client ne libère (lecture) pas de données dans le pipe (le circuit virtuel entre SQL Server et la connexion client).

Si une connexion client retarde le traitement des données qui lui sont envoyées, des accès concurrents se produisent car les verrous sont maintenus plus longtemps qu'ils ne le devraient. Une réaction en chaîne se produit: si la connexion client ne lit pas plusieurs packet réseaux, les prochaines écritures du buffer de sortie côté SQL Server sont mises en attente parce que le pipe n'est pas vidé. Tant que le buffer de sortie n'est pas disponible, le scan des données peut aussi être suspendu car il n'y a plus de place pour y ajouter un enregistrement. Tant que le scan est maintenu, aucun verrou sur les données ne peut être libéré. En conclusion, sur l'application client ne traite pas les jeux de résultats régulièrement, les concurrences d'accès à la base en souffrent.

La taille du buffer réseau peut aussi affecter la vitesse à laquelle le client reçoit le premier jeu de résultat. Comme mentionné précédemment, le buffer de sortie est envoyé lorsque le traitement, pas simplement la commande, se termine, même si le buffer n'est pas plein. (Un traitement ou batch est une ou plusieurs commandes envoyées à SQL Server pour y être analysées et exécutées ensemble. Par exemple, si on utilise OSQL.EXE, un batch est l'ensemble de toutes les commandes à exécuter avant la commande GO.) Si deux requêtes existent dans le même batch et que la première requête ne produit que peu de données, le jeu de résultat n'est pas envoyé au client tant que la seconde requête est terminée ou a fourni suffisamment de pour remplir le buffer de sortie. Si les deux requêtes sont rapides, ce n'est pas un problème. Mais supposons que la première est rapide et la seconde lente. Et supposons que la première requête retourne 1000 octets de données. Si la taille du packet réseau est de 4096 octets, le premier jeu de résultat doit attendre dans le buffer de sortie que la seconde requête le remplisse. La solution adaptée consiste à faire exécuter la première requête dans son propre batch ou bien de réduire la taille du buffer réseau. La première solution est la mieux adaptée car il est difficile de déterminer la meilleure taille de buffer pour chaque commande. Mais cela ne signifie pas que chaque commande doit posséder son propre batch. Bien au contraire, dans certaines circonstances, le groupement de plusieurs commandes dans un même batch est plus efficace et recommandé parce qu'il réduit le nombre d'aller retour entre le client et le serveur.

Les Net-Libraries par défaut de ODS

Par défaut, SQL Server sur Windows NT écoute toujours sur les pipes nommés, TCP/IP et Multiprotocol. SQL Server sur Windows 95 et Windows 98 écoute sur Shared Memory plutôt que sur pipes nommés, mais a aussi TCP/IP et Multiprotocol. On peut ajouter d'autres interfaces Net-Library. Sur Windows NT, on peut aussi supprimer n'importe quelle Net-Libraries, mais il vaut mieux ne pas supprimer pipes nommés. Toutes les autres Net-Libraries sur Windows NT requièrent un réseau. Du fait de l'existence des services pipes nommés dans Windows NT même sans la présence d'un réseau, l'utilisation de pipes nommés laisse une porte d'accès à SQL Server même si le réseau pose problème. De même, SQL Server sur Windows 95 et Windows 98 écoute toujours sur Shared Memory IPC par défaut, donc il ne faut pas supprimer cette option. Même sans réseau, ce qui est le plus souvent le cas avec Windows 95 et Windows 98 qu'avec Windows NT, Shared Memory est toujours disponible comme IPC.

La Figure 3-2 montre le chemin depuis l'application client SQL Server ver le moteur SQL Server and montre là où l'interface Net-Library s'intègre. Sur la partie serveur, ODS fournit les fonctionnalités qu'offrent ODBC, OLE DB ou DB-Library sur le client. Les appels ODS vers une application serveur ODS se font pour décrire et envoyer des jeux de résultats, pour convertir des types de données, pour gérer un contexte de sécurité associé à une connexion spécifique, et pour déclencher des erreurs et des messages vers l'application client.

ODS utilise un modèle de programmation par événement. Les requêtes des serveurs et des clients déclenche des événements auquel l'application serveur doit répondre. En utilisant l'API ODS, cela se fait en créant des routines spécialisées, appelées event handler, pour chaque type d'événement. En fait, ODS pilote l'application serveur en appelant ces routine d'événements spécialisées en réponse aux requêtes entrantes.

Figure 3-2. Le chemin depuis une application client SQL Server vers le moteur SQL Server.

Les applications serveur ODS répondent aux événements suivants:

ODS génère aussi des événements basés sur les activités de certains clients et certaines applications. Ces événements permettent à une application serveur ODS de répondre aux changements d'état d'une connexion client ou d'une application serveur ODS.

En plus de la gestion des connexions, ODS gère les threads (et fibers) pour SQL Server. Il gère la création et la destruction des threads et rend les threads accessibles à UMS (User Mode Scheduler). L'API ODS permet aux ISVs d'écrire des applications serveur comme SQL Server qui tirent partie des mêmes bénéfices que SQL Server hérite de ce composant, comme la gestion SMP des threads et la gestion des connexions réseaux multi-protocoles. Ces opérations multi-threads permettent aux applications serveur ODS d'obtenir un haut niveau de performance et l'utilisation de processeurs multiples sous Windows NT car le système d'exploitation peut ordonnancer n'importe quel thread sur n'importe quel processeur disponible.

Le Moteur Relationnel et le Moteur de Stockage

Le moteur de base de données SQL Server est constitué de deux modules principaux, le moteur relationnel et le moteur de stockage. Contrairement aux versions précédentes de SQL Server, ces deux pièces sont clairement séparées, et communiquent via OLE DB. Le moteur relationnel contient tous les composants nécessaires à l'analyse et l'optimisation de n'importe quelle requête. Il acquiert les données au près du moteur de stockage sous forme de rowsets OLE DB, les traitent et les retournent. Le moteur de stockage contient les éléments nécessaires à l'accès et la modification des données sur disque.

L'Analyseur de Commandes

L'analyseur de commandes gère les événements de langage déclenchés par ODS. Il vérifie la syntaxe et transforme les commandes Transact-SQL dans un format interne. Ce format interne est un arbre de requête (query tree). Si l'analyser ne reconnaît pas la syntaxe, une erreur de syntaxe est levée et un message d'erreur indique la ligne exacte où l'erreur s'est produite. Cependant, les messages d'erreur des non-erreurs de syntaxe ne peuvent déterminer la ligne de source à partir de laquelle il y a problème. Parce que seul l'analyser peut accéder au code source de la requête, elle n'est plus au format source quand elle est exécutée. Les exceptions à cette séquence d'appel de l'analyseur commande sont EXECUTE("string") et les opérations sur les curseurs. Ces deux opérations peuvent appeler l'analyseur récursivement.

L'Optimiseur

L'optimiseur prend l'arbre de requête de l'analyseur de requête et le prépare pour l'exécuter. Ce module compile le batch entier, optimise les requêtes, et vérifie les sécurités. L'optimisation et la compilation de requête fournissent un plan d'exécution.

Le premier pas dans la production d'un tel plan est la normalisation de la requête, qui potentiellement casse une requête simple en plusieurs requêtes. Une fois que l'optimiseur a normalisé la requête, il l'optimise ce qui veut dire que l'optimiseur détermine un plan pour exécuter cette requête. Les optimisations de requêtes sont basées sur les coûts; l'optimiseur choisit un plan qui réduit l'utilisation de la mémoire, du temps CPU et du nombre de I/Os. Il considère le type de requête, vérifie le total de données dans les différentes tables demandées, regarde les indexes disponibles pour chaque table, et examine les indexes et les colonnes référencées dans la requête. Il recueille ainsi divers données appelées statistics. Basé sur ces informations, l'optimiseur considère les méthodes d'accès et les stratégies de jointure qu'il pourrait utiliser pour résoudre la requête au coût le plus faible. L'optimiseur décide des indexes à utiliser pour chaque table de la requête, et dans le cas d'une requête multi-tables, l'ordre avec lequel les tables doivent être accédées, et la stratégie de jointure.

L'optimiseur utilise des principes statistiques pour assurer qu'il ne passe pas plus de temps à optimiser la requête qu'à choisir un plan et à l'exécuter. Il ne fait pas forcement d'optimisation. Une requête exprimée de diverses manières syntaxiques peut aboutir au même plan d'exécution. L'optimiseur travaille par rapport à des règles d'optimisation.

L'optimiseur SQL Server est basé sur les coûts. Cependant, il se base sur des probabilités pour choisir un plan d'exécution, et des fois, il a tord. L'optimiseur ne sera jamais parfait et il est possible d'influencer son choix en lui précisant l'index à utiliser ou une stratégie de jointure.

Après la normalisation et l'optimisation, l'arbre normalisé produit est compilé en un plan d'exécution, qui est une structure de données. Chaque commande incluse spécifie exactement quelle table doit être affectée, quels indexes seront utilisés, quelles vérifications de sécurité seront opérées, et quels critères devront être évalués à VRAI pour être sélectionnés. Ce plan d'exécution peut être complexe. En effet, il contient aussi les contraintes à vérifier. Les étapes d'appel d'un déclencheur (trigger) sont différentes de celles de la vérification d'une contrainte. Si un déclencheur est inclus dans les actions à mener, un appel à la procédure qui contient ce déclencheur est ajouté. Ce déclencheur a son propre plan d'exécution qui est branché juste avant la validation (commit). Les phases spécifiques du déclencheur ne sont pas compilées dans le plan d'exécution, comme les vérifications de contraintes.

Une simple requête qui inclut un enregistrement (row) dans une table avec plusieurs contraintes peut provoquer l'accès à d'autres tables ou l'évaluation d'autres expressions. L'existence d'un déclencheur peut demander l'exécution de phases additionnelles. Cette étape qui prend en charge l'ordre INSERT peut juste être une petite partie du plan d'exécution qui nécessite la vérification de toutes les actions et contraintes associées à l'ajout d'un enregistrement dans une table.

Le gestionnaire SQL

Le gestionnaire SQL est responsable de la gestion des procédures stockées et de leurs plans. Il détermine lorsqu'une procédure doit être recompilée suite au changement de schéma des objets utilisés, et il gère le cache des plans de procédures pour qu'ils puissent être réutilisés par d'autres processus.

Le gestionnaire SQL gère aussi les paramètres automatiques des requêtes. Certaines sortes de requêtes sont traitées comme des procédures stockées paramétrées, et des plans de requêtes sont générés. C'est le cas pour une requête qui possède une comparaison avec une constante.

SELECT * FROM pubs.dbo.titles 
WHERE type = 'business'

Cette requête peut être paramétrée comme s'il s'agissait d'une procédure stockée avec un paramètre pour la valeur type:

SELECT * FROM pubs.dbo.titles 
WHERE type = @param

Le plan de requête généré est le même que pour la requête originale.

Le Gestionnaire d'Expression

Le gestionnaire d'expression gère le calcul, la comparaison et le mouvement de données.

SELECT @myqty = qty * 10 FROM mytable V 

Le service d'expression copie la valeur de qty du rowset retourné par le moteur de stockage, la multiplie par 10, et stocke le résultat dans @myqty.

L'Exécuteur de Requête

L'exécuteur de requête exécute le plan d'exécution qui a été produit par l'optimiseur, comme un dispatcher pour toutes les commandes du plan. Ce module itère dans le plan d'exécution jusqu'à ce que le batch soit terminé. La plupart des commandes utilisent le moteur de stockage pour modifier ou obtenir des données et gérer des transactions et des verrous.

Communication entre le Moteur Relationnel et le Moteur de Stockage

Le moteur relationnel utilise OLE DB pour la plupart de ses communications avec le moteur de stockage. La description suivante de cette communication est adaptée de la documentation SQL Server. Elle décrit comment un ordre SELECT qui agit sur des tables locales est traité:

Toutes les communications entre le moteur relationnel et le moteur de stockage ne sont pas faites avec OLE DB. Certaines commandes ne peuvent s'exprimer en termes de rowsets, par exemple, lorsque le moteur relationnel traite les requêtes DDL (data definition language) pour créer une table ou un autre objet SQL Server.

Le Gestionnaire de Méthodes d'Accès

Lorsque SQL Server a besoin de localiser les données, il appel le gestionnaire de méthodes d'accès. Ce gestionnaire de méthodes d'accès construit et demande des scan de pages de données et de pages d'index et prépare les rowsets OLE DB en retour au moteur relationnel. Il contient les services pour ouvrir une table, obtenir des données, et modifier les données. Le gestionnaire de méthodes d'accès n'acquiert pas les pages; il fait la demande au gestionnaire de buffer, qui contient la page dans son cache ou sur disque. Lorsque le scan est démarré, un mécanisme d'anticipation lit les enregistrements et les entrées d'index sur une page. L'obtention des enregistrements qui répondent aux critères de requêtes, est connu sous le nom de qualified retrieval

Une session ouvre une table, demande et évalue une série d'enregistrements par rapport à la clause WHERE, et ferme la table. SDES (session descriptor data structure) conserve l'enregistrement courant et la condition de recherche de l'objet sur lequel l'opération est réalisée. L'objet est identifié par un DES (descriptor data structure).

Le Gestionnaire d'Opérations d'Enregistrements et le Gestionnaire d'Index

Le gestionnaire d'opérations d'enregistrements et le gestionnaire d'index peuvent être considérés comme des composants du gestionnaire de méthodes d'accès. Chacun est responsable de la manipulation et du maintient de ses structures de données sur disque, les enregistrements de données et les arbres B indexés. Iil comprennent et manipulent les informations sur les pages données et les pages de données.

Le Gestionnaire d'Opérations d'Enregistrements

Le gestionnaire d'opérations d'enregistrements  obtient, modifie et réalise des opérations sur des enregistrements individuels. Il réalise des opérations sur un enregistrement comme l'obtention de la colonne 2 ou l'écriture d'une valeur dans la colonne 3. En tant que le résultat du travail du gestionnaire de méthodes d'accès, du gestionnaire de verrous, et du gestionnaire de transactions, l'enregistrement est trouvé et verrouillé au sein d'une transaction. Après le formatage et la modification d'un enregistrement en mémoire, le gestionnaire d'opérations d'enregistrements insert ou supprime l'enregistrement.

Le gestionnaire d'opérations d'enregistrements gère aussi les modifications. SQL Server 7 offre trois méthodes pour gérer les mises à jour. Toutes les trois sont directes, et ne passe donc pas par le journal des transactions. SQL Server 7 n'a pas le concept d'opération de mise à jour des données déférée.

Les trois modes de mises à jour dans SQL Server 7 sont:

Si vous voulez réorganiser une table— par exemple, pour rétablir une valeur FILLFACTOR ou pour faire que les données soient plus contiguës après beaucoup de modifications de données— vous pouvez utiliser un index cluster, ce qui rend la réorganisation plus facile. Vous reconstruisez simplement l'index cluster, ce qui reconstruit toute la table. Dans le cas d'une suppression, si un enregistrement supprimé est le dernier de la page de données, cette page est libérée. La seule exception est que la page soit la seule de la table. Une table contient au moins un page, même si elle est vide.

Le Gestionnaire d'Index

Le gestionnaire d'index gère les recherche sur les arbres B, qui sont utilisés pour les indexes SQL Server. Un index est structuré en un arbre, avec une page principale et intermédiaire and des pages au niveau inférieur, des branches. Un arbre B groupe les enregistrements qui ont des clés d'index similaires, permettant ainsi un accès rapide à la donnée en cherchant sur la valeur d'une clé. La fonctionnalité majeure d'un arbre B est sa capacité à balancer l'arbre d'index. Les branches de l'arbre d'index sont organisées ou séparées pour que la recherche de n'importe quel enregistrement demande le même nombre de niveau à parcourir et donc requiert le même nombre d'accès aux pages.

La traversée commence depuis la page principale, progresse vers les niveaux d'index intermédiaires, et finalement se déplace sur les pages du bas appelées pages feuilles. L'index est utilisé pour trouver la page feuille correcte. La page feuille correcte est la page la plus basse de l'arbre sur laquelle un ou plusieurs enregistrements avec la clé ou les clés spécifiées sont.  SQL Server supporte les indexes cluster et non-cluster. Dans un index non-cluster montré en Figure 3-3, le niveau feuille de l'arbre (la page feuille de l'index) contient chaque valeur de clé dans l'index avec un pointeur d'enregistrement pour chaque valeur. Ce pointeur d'enregistrement est un bookmark et indique ou sont référencées les données. Un bookmark peut avoir deux formes. Si la table a n'a pas d'index cluster, la table est référencée comme un heap. Les bookmarks dans les pages feuille d'un index cluster sont des pointeurs vers les enregistrements dans lequel les données sont identifiées, et ces pointeurs consistent en un RID (Row ID), avec un numéro de fichier, un numéro de page, et un numéro d'enregistrement sur la page. Si la table a un index cluster, les bookmarks dans n'importe quelles pages feuille non cluster contiennent la valeur de la clé d'index cluster de l'enregistrement.

Avec le niveau feuille dans un index non- cluster, vous pouvez trouver la localisation exacte des données, bien que la page sur laquelle les données résident doit être obtenue séparément. Parce que vous accéder directement aux données, vous n'avez pas besoin de faire un scan de toutes les pages de données pour chercher un enregistrement. Dans un index cluster, montré en Figure 3-4, la page feuille contient l'enregistrement, pas simplement la clé d'index. Un index cluster garde les données physiquement ordonnées dans la table à coté de la clé d'index cluster, et la page feuille d'un index cluster est en fait la page de données.

Parce que les données peuvent être physiquement ordonnées que dans un seul sens, il ne peut exister qu'un seul index cluster par table. Cela rend la sélection de la valeur des clés appropriées à la donnée cluster une considération de performance importante.

Vous pouvez aussi utiliser les indexes pour assurer le caractère unique d'une valeur de clé. Les contraintes PRIMARY KEY et UNIQUE sur une colonne créent un index unique sur les valeurs des colonnes. L'optimiseur utilise le fait qu'un index soit unique dans un plan d'exécution efficace.

En interne, SQL Server assure que les indexes cluster sont uniques en ajoutant un "uniqueifier" à la valeur de la clé d'index. Cet identifiant fait parti de la clé et est utilisé dans tous les niveaux d'un index cluster et dans les références à une clé d'un index cluster par les indexes non-cluster.

Bien que SQL Server maintienne un ordre dans les niveaux feuille d'index, vous n'avez pas besoin de décharger et recharger les données pour maintenir les propriétés cluster des données ajoutées ou déplacées. SQL Server insert les enregistrements dans les pages correctes. Pour un index cluster, la page feuille correcte est la page de données dans laquelle l'enregistrement sera inséré. Pour un index non-cluster, la page feuille correcte est celle dans laquelle SQL Server insère un enregistrement contenant la valeur de clé and un bookmark de données pour le nouvel enregistrement. Si les données sont mises à jour and que les valeurs de clé d'un index changent, ou si l'enregistrement est déplacé vers une autre page, le contrôle des transactions SQL Server assure les modifications. Sous le contrôle des transactions, les opérations sur les indexes sont atomique. Elles sont enregistrées et rejouées dans le cas d'un crash système.

Figure 3-3. Un index non-cluster avec un niveau feuille contenant des bookmarks: RID ou valeur de clé d'index cluster.

Figure 3-4. Un index cluster avec les données situées dans la page feuille.

Le Gestionnaire de Pages et le Gestionnaire de Textes

Le gestionnaire de pages et le gestionnaire de textes coopèrent pour gérer une collection de pages de bases de données. Chaque base est une collection de pages de 8 KB sur disque, qui sont réparties entre un ou plusieurs fichiers physiques.

SQL Server utilise six types de pages disque: les pages de données, les pages image/text, les pages d'index, les pages PFS (Page Free Space) , les pages GAM et SGAM (Global Allocation Map), les pages IAM (Index Allocation Map). Toutes les données utilisateur, sauf les types de données text et image, sont stockées dans les pages de données. Les types text et image, qui sont utilisés pour stocker des objets volumineux (jusqu'à 2 GB par donnée text ou binaire), utilise une collection de page séparée. Un pointeur sur la page de données standard identifie la page de départ et l'offset de la donnée text/image. Les pages d'index stockent l'arbre B pour accéder rapidement aux données. Les pages PFS conservent la trace des pages de bases qui peuvent contenir de nouvelles données. Les pages d'allocation (GAMs, SGAMs, et IAMs) conserve la trace des autres pages. Elles ne contiennent aucun enregistrement et sont utilisées en interne seulement.

Le gestionnaire de pages alloue et libère tous les types de pages disque, gérant les extensions (extend) de huit pages chacune. Une extension peut être uniforme, pour laquelle huit pages sont allouées pour le même objet (table ou index), ou bien mixée, qui peut contenir les pages d'objets multiples. Si un objet utilise moins de huit pages, le gestionnaire de pages alloue les nouvelles pages pour cet objet depuis une extension mixée. Quand la taille de l'objet dépasse huit pages, le gestionnaire de pages alloue un nouvel espace pour cet objet avec des extensions uniformes. Cette optimisation permet de diminuer le nombre d'allocation nécessaire à chaque nouvelle page requise pour une grande table; elle est nécessaire seulement toutes les huit fois. L'apport de cette optimisation fait que les données d'une même table sont contiguës la plupart du temps. De même, l'utilisation extension mixée permet à SQL Server de perdre de la place pour les bases avec de petites tables.

Pour déterminer si les données d'une table sont contiguës, vous pouvez utiliser la commande DBCC SHOWCONTIG. Une table avec beaucoup d'allocation et de libération peut rapidement devenir fragmenté, et la reconstruction de l'index cluster (ce qui reconstruit également la table) améliore les performances d'accès, spécialement pour les opérations de scan de tables.

Le Gestionnaire de Transactions

Les transactions ont les propriétés ACID. Les transactions sont atomique— tout ou rien. Si une transaction est validée, elle doit être récupérable par SQL Server quel que soit le problème qui peut survenir après— même si un crash système intervient une milliseconde après. Dans SQL Server, si le crash système intervient avant la validation de la transaction, les modifications ne sont pas appliquées. L'écriture dans un journal permet d'annuler ou de valider les changements qui n'ont pas été affectés dans les pages de données. L'écriture au journal assure que les changements induits par la transaction—l'image avant et après des données— sont capturées sur disque dans le journal des transactions (transaction log) avant que la transaction soient reconnue comme validée. Les écritures au journal sont toujours synchrones— SQL Server doit attendre avant de continuer. Les changements aux pages de données peuvent être asynchrones car les modifications peuvent être réappliquées à partir du journal si nécessaire. Le gestionnaire de transaction coordonne la journalisation, la récupération, et la gestion des buffers.

Le gestionnaire de transaction repère les ordres à grouper pour une réaliser une opération. Il gère les transactions entre les bases d'un même SQL Server, et permet les séquences de transactions imbriquées. (Cependant, les transactions imbriquées s'exécutent dans le contexte du premier niveau de transaction; aucune action spéciale ne se produit lorsqu'elles sont validées. Une annulation de transaction à un niveau inférieur dans une transaction imbriquée annule toute la transaction.) Pour les transaction distribuées vers une autre SQL Server (ou vers n'importe quel gestionnaire de ressource ou resource manager), le gestionnaire de transaction coordonne le tout avec le service MSDTC (Microsoft Distributed Transaction Coordinator)  (MS DTC) en utilisant les procédures stockées distantes du système d'exploitation. Le gestionnaire de transaction pose des savepoints, qui désignent des points dans la transaction à partir duquel une validation ou annulation partielle  peut se faire.

Le gestionnaire de transaction coordonne aussi le gestionnaire de verrous, qui détermine lorsque les verrous peuvent être libérés, basé sur le niveau d'isolation (isolation level) positionné. Le niveau d'isolation dans lequel s'exécute la transaction détermine le comportement de votre application face aux changements des autres applications, et la durée de maintient de la transaction. Il existe quatre niveaux d'isolation dans SQL Server 7: Uncommitted Read (ou dirty read), Committed Read, Repeatable Read, et Serializable.

Le comportement d'une transaction dépend du niveau d'isolation. La compréhension complète de ces niveaux d'isolation passe aussi par un la connaissance des verrous.

Uncommitted Read

Uncommitted Read permet à la transaction de lire les données présentent sur une page de données, que les données soient validées ou non. Par exemple, un autre utilisateur peut avoir une transaction en cours qui modifie des données, et malgré l'obtention d'un verrou exclusif sur les données, votre transaction peut quand même lire les données. L'autre utilisateur peut décider d'annuler les changements réalisés dans sa transaction, pour qu'ils ne soient pas effectifs. Dans un système mono-utilisateur utilisateur ou toutes les opérations sont sérialisées, les changements ne seraient pas visibles à d'autres utilisateurs. Dans un système multi-utilisateurs, cependant, vous pouvez lire les changements. 

L'utilisation du mode Uncommitted Read n'assure pas que le contenu des données soit consistant car la concurrence d'accès fait que les utilisateurs ne se bloquent pas les uns les autres. Cependant, ce mode n'est pas adapté pour les transactions financières, mais peut être adapté pour l'analyse décisionnelle.

Committed Read

Committed Read est le niveau d'isolation par défaut de SQL Server. Il assure qu'une opération ne pourra pas lire des données qu'une autre application modifie et dont les changements ne sont pas encore validés. (Donc, il ne lira pas des données qui théoriquement, n'existent pas.) Avec Committed Read, si une transaction qui modifie des données possède un verrou exclusif sur des enregistrements, votre transaction doit attendre la libération de ces verrous avant de pouvoir accéder à ces données (que ce soit en lecture ou en écriture). De plus, votre transaction  doit positionner un verrou partagé (au minimum) sur les données à utiliser, ce qui signifie que les données sont potentiellement inaccessibles à d'autres. Un verrou partagé n'empêche pas les autres de lire les données, mais ils sont mis en attente pour toute modification. Les verrous partagés sont libérés après que les données sont renvoyées au client— ils ne sont pas maintenus pendant la durée de la transaction.

NOTE
Bien qu'on ne puisse pas lire les données non validées, si une transaction avec le niveau d'isolation Committed Read repasse sur les même données, les données peuvent avoir changé ou de nouveaux enregistrements peuvent apparaître qui satisfont aux critères de la requête. Ces enregistrements sont des phantoms.

Repeatable Read

Le niveau d'isolation Repeatable Read ajoute aux propriétés du mode Committed Read que si la transaction revisite les données, les données ne changent pas. Donc, la même requête présente deux fois dans une transaction ne verra pas les modifications apportées la transaction d'un autre utilisateur.

Ce mode est n'est pas gratuit pour le système. Le surcoût est que les verrous partagés dans la transaction doivent être conservés jusqu'à la fin (COMMIT ou ROLLBACK) de la transaction. (Les verrous exclusifs doivent toujours être maintenus jusqu'à la fin de la transaction, quel que soit le niveau d'isolation, de telle manière que la transaction puisse être annulée si nécessaire. Si les verrous étaient libérés plus tôt, ce serait impossible de réaliser l'opération.) Aucun autre utilisateur ne peut modifier les données visées par votre transaction tant qu'elle est en cours. Cela peut sérieusement dégrader les performances et réduire les concurrences d'accès. Ces les transactions ne sont pas courtes et si les applications ne sont pas écrites en tenant compte de ces problèmes de blocage, SQL Server peut geler lors de l'attente d'une libération de verrou.

NOTE
Il est possible de déterminé combien de temps d'attente de SQL Server pour qu'un verrou soit libéré en utilisant la session LOCK_TIMEOUT.

Serializable

Le niveau d'isolation Serializable ajoute aux propriétés du mode Repeatable Read que si la requête est soumise une nouvelle fois, les phantoms n'apparaîtront pas dans la seconde requête dans la transaction. Repeatable Read et Serializable ont une incidence sur les changements apportés par les autres connexions, que le user ID de l'autre connexion soit le même ou non. Chaque connexion avec SQL Server a son propre espace de transactions et de verrous.

Ce mode n'est pas gratuit. C'est la même remarque que pour le mode Repeatable Read— tous les verrous partagés dans la transaction doivent être maintenus jusqu'à la fin de la transaction. Ce mode assure aussi que vous ne verrouillez pas les données lues, mais aussi les données qui n'existent pas! Par exemple, supposons que dans une transaction, un ordre SELECT soit utilisé pour lire les clients dont le code postal se situe entre 98000 et 98100, et que la première exécution ne retourne aucun enregistrement. Pour que le mode Serializable soit assuré, il faut verrouiller une série potentielle d'enregistrements avec des codes entre 98000 et 98100; ainsi lorsque la requête est soumise une nouvelle fois, il y aura toujours aucun enregistrements qui satisfont à la recherche. SQL Server gère ce cas avec un type de verrou particulier appelé range lock. Le mode Serializable vient du fait que si que si une transaction est exécutée plusieurs fois au même moment, c'est comme si chacune d'elle était exécutée une par une. Par exemple, les transactions A, B et C sont serializable seulement si le résultat obtenu est les lançant simultanément; est le même que si elles sont lancées une par une, dans n'importe quel ordre. L'ordre n'a pas d'importance. Si l'ordre est important, il faut utiliser un batch.

Le Gestionnaire de Verrous

Le verrouillage est une fonction critique dans un système de bases de données multi-utilisateurs utilisateur comme SQL Server. SQL Server gère plusieurs utilisateurs simultanément et assure que les transactions réagissent différents suivant le type de niveau d'isolation. Au un plus haut niveau, le mode Serializable, SQL Server fait que le système multi-utilisateurs utilisateur agisse comme un système mono-utilisateur utilisateur— comme si chaque utilisateur utilisait le système tout seul sans aucune activité d'autres utilisateurs. Le verrouillage des données et les ressources internes rendent l'opération possible, et permettent que plusieurs utilisateurs accèdent en simultané à la base et ne soient pas affectés trop lourdement par l'utilisation des autres utilisateurs.

Le gestionnaire de verrous alloue et libère différents types de verrous, comme les verrous partagés en lecture, les verrous exclusifs en écriture, les verrous intent pour signaler un plan potentiel pour réaliser des opérations, les verrous extent pour l'allocation de place... Il gère la compatibilité entre les types de verrous, résout les interblocages, et enchaîne les verrous si nécessaire. Il contrôle les verrous de table, de page et d'enregistrement aussi bien que les verrous des données systèmes. (Les données système, comme les entête de page et les indexes, privés au système de base de données.)

Le gestionnaire de verrous fournit deux systèmes séparés de verrouillage. Le premier permet les verrous de type enregistrement, table pour toutes les données des tables complètement partagées, pages de données et enregistrements, pages textes et pages d'index de niveau feuille et enregistrements d'index. Le second système de verrouillage est utilisé en interne seulement pour les données systèmes; il protège les pages d'index principales et intermédiaires pendant que les indexes sont traversés. Ce mécanisme interne utilise les latches, a verrou allégé qui protège les données qui n'ont pas besoins d'être verrouillées pendant la durée d'une transaction. Les verrous complets écrouleraient le système. En plus de protéger les niveaux supérieurs des indexes, les latches protègent les enregistrements pendant qu'ils sont transférés du moteur de stockage au moteur relationnel. Si vous examinez les verrous en utilisant la procédure stockée sp_lock ou un mécanisme similaire qui obtient des informations sur la table syslockinfo, vous ne verrez pas les latches; vous ne verrez que les verrous sur des données complètement partagées. Cependant, certains compteurs du moniteur de performance sont disponibles pour superviser les demandes de latches, les acquisitions et les libérations. 

Le verrouillage est un aspect important de SQL Server. La plupart des développeurs s'intéressent à ces mécanismes à cause de ces effets occasionnés sur les performances des applications.

Les Autres Gestionnaires

Il existe d'autres gestionnaires incluent dans le moteur de stockage comme la gestion des chargements en masse (bulk load), les commandes DBCC, les opérations de sauvegarde et de restauration, et le VDI (Virtual Device Interface). Le VDI permet aux ISVs de développer leurs propres outils de restauration et de sauvegarde et d'accéder aux structures de données de SQL Server, sans passer par le moteur relationnel. Il y a aussi un gestionnaire pour contrôler les opérations de tri et un autre qui gère physiquement les fichiers et les unités (device) de sauvegardes sur disque.