Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Optimiser : Utilisation des index - Améliorer des tris

Lors de mon dernier billet sur la performance dans SQL Server, nous avons vu l'amélioration d'une simple recherche de données via des index essentiellement de type non clustered : http://blogs.codes-sources.com/christian/archive/2008/01/15/optimiser-utilisation-des-index-am-liorer-une-simple-recherche.aspx

Cette fois ci nous voir comment améliorer des tris.

Je vais créer une table sur le même modèle que la dernière fois, mais avec 2 champs supplémentaires, eux aussi remplis de manière aléatoire :

IF OBJECT_ID('dbo.MaTable') IS NOT NULL
DROP TABLE dbo.Matable

CREATE TABLE dbo.MaTable
(
Id int identity(1,1),
Nom varchar(100), 
Nom2 varchar(100), 
Nom3 varchar(100)
)
GO

DECLARE @var int
SET @var = 0

INSERT INTO dbo.MaTable(Nom)
VALUES('0000000000')

WHILE @var < 18
BEGIN

INSERT INTO dbo.MaTable(Nom, Nom2, Nom3)
SELECT CAST(@var as varchar(10)), CAST(NEWID() as varchar(50)), CAST(NEWID() as varchar(50)) FROM dbo.MaTable

SET @var = @var + 1

END
GO

Indépendamment de la technique que j'utilise, mes données ne sont pas triées dans ma table… Ici il se trouve que ma méthode de remplissage me donne un résultat à peu près trié par rapport au champ Id, mais dès le premier mouvement de données je suis susceptible de me retrouver avec tout autre chose.

      -- Résultat ici totalement aléatoire 
      
select
      
from dbo.MaTable

La seule garantie de retrouver les données triées est d'ajouter a votre requête la clause ORDER BY et c'est impératif, faute de quoi les erreurs qui serait engendrées pourraient se révéler catastrophiques.

select *
from dbo.MaTable
order by Id

Nous donnera un plan très simple

La différence entre la requête réalisant le tri et l'autre est uniquement de l'autre du temps processeur… Le tri pour cette requête nous consomme environ 200ms de processeur. C'est tout à fait normal, la majeure partie du temps les données à trier peuvent l'être en mémoire, et le tri c'est de l'algorithme qui consomme uniquement du processeur.

On voit nettement apparaître que dans un plan à base d'ORDER BY la tâche la plus consommatrice de ressource est le tri. Sur une machine a base de cœurs multiple SQL Server décide même de réaliser le tri en parallèle sur les 2 unités de calcul, ce qui provoque une réduction de la durée d'exécution de la requête, mais pas de la consommation de CPU (au contraire).

Ajoutons un index sur le champ Id, pour voir si nous obtenons un gain :

create nonclustered index ix_test on dbo.MaTable(Id)

Comme la dernière fois, pas de surprise, pas d'utilisation de l'index le plan reste le même… Mais est ce normal ?

En fait oui, un index réalise un tri lors de sa création, mais uniquement du champ sur lequel il est défini… Ici nous avons donc l'ensemble du champ Id qui est trié et stocké dans la base de donnée dans l'index ix_test, mais la moteur ne peut l'utiliser, car il n'a pas besoin que du champ Id, mais aussi de tous les autres de la table. Cela reviendrait si je forçais l'utilisation de cet index à :

select *
from dbo.MaTable with(index = ix_test)
order by Id

Le fait de forcer l'index ici me donne le plan suivant :

Ce qui saute aux yeux c'est la taille des flèches qui est gigantesque… Et le résultat c'est que mon moteur pour exécuter une telle requête doit traiter 262635 alors que la table n'en comporte que 3085. Le fait de forcer l'utilisation de l'index a supprimé la tâche de tri en contrepartie de quoi, le moteur a du utiliser l'index pour tous les enregistrements présent dans la table, soit 85 fois plus de données que nécessaire.

Clairement ici l'utilisation de ce type d'index n'est pas l'utilisation et le moteur s'en est rendu compte bien avant nous ! Par contre une variante de la requête va s'avérer intéressante :

select Id
from dbo.MaTable
order by Id

select *
from dbo.MaTable

Cela nous donne le même plan, mais avec une variante dans l' « Index Scan »

Quand on regarde de plus près de qu'il y a dans les propriétés de l'Index Scan, on constate que la ligne « Ordered » a changé entre les 2 requêtes !

 

 

 

En passant le nombre de lecture est lui tombé de manière vertigineuse à 491 pages, et le temps CPU à environ 50 ms. Le gain est très net ici par rapport à notre requête sans index.

Ce qui s'est passé est tout simple, notre requête n'a besoin que d'un seul champ : Id, il se trouve que ce champ est déjà indexé et donc que le champ Id y est trié… Donc le moteur n'a rien d'autre à faire qu'à lire l'index et renvoyer le résultat.

Mais, il y a un mais, il n'y a aucune garantie que malgré le tri de l'index (tri qui est réalisé de manière logique, chaque page pointe sur la page suivante et la précédente pour s'assurer de l'ordre des données présentes dans l'index) les données soient triées correctement (physiquement) sur le disque. Or çà n'a pas de sens pour mon moteur de réaliser des lectures page par pages en fonction de leur ordre directement depuis le disque, c'est beaucoup trop couteux. On réalise alors une lecture séquentielle (indépendante de l'ordre des données) pour charger toutes les pages en mémoire.

C'est alors qu'intervient le « Ordered » à True ou False… Si l'on spécifie ORDER BY Id, il est à True, ce qui force le moteur à remettre les données dans l'ordre, mais sans réaliser une couteuse tâche de tri, juste en suivant l'ordre des pages en mémoire (par les liens internes des pages d'index), cette tâche est extrêmement dépendante de la fragmentation des données… Par contre dans le cas où nous ne souhaitons pas trier nos données, le « Ordered » est laissé à False, pas d'ordre à rétablir, les données sont renvoyées telles quelles.

Comment réaliser le même type de chose, avec l'ensemble des données de la table et donc supporter le SELECT * que j'ai spécifié au début.

En utilisant un Index dit « Clustered » :

create clustered index ix_test2 on dbo.MaTable(Nom)

Ici je le crée sur le champ Nom pour bien vous montrer son comportement en dehors du cas classique où il est utilisé. La requête associée :

select *
from dbo.MaTable
order by Nom

Donne le plan :

L'index est bien utilisé, le « Ordered » est bien à True, et la tâche de trie est absente du plan. On a donc bien un gain à l'utilisation de cet index. Regardons le nombre de pages lues, et là par contre c'est un peu la déception car nous lisons 3290 pages, un peu plus que lorsque nous lisions la table seule au début.

En fait l'index clustered présente une caractéristique particulière. Là où les autres index ne stockent que les données de la clef ou les clefs sur lesquelles ils sont définis (ici le champ nom), le clustered contient tous les champs de la table. Cela explique l'index clustered soit un peu plus volumineux que les données même de la table.

Il nous reste toujours un problème, si je veux faire un tri sur l'un des autres champs de ma table :

select *
from dbo.MaTable
order by Nom2

Le plan :

Dès qu'un index clustered est crée sur la table je suis obliger de passer par lui pour tout accès aux données. Ce qui explique que je n'ai pas de « Table Scan », mais un « Index Scan » marqué « Ordered » à False, et la tâche de tri est réapparue dans le plan.

L'index clustered nous rend bien des services, mais je n'ai le droit d'un mettre 1 et un seul… C'est d'ailleurs bien souvent, le problème. Sur quel champ le mettre ?

On verra qu'il existe des alternatives à cette limitation d'index clustered unique…

Bonnes performances…

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Publié mardi 19 février 2008 09:00 par christian

Commentaires

Pas de commentaires
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- [Silverlight] En attendant Silverlight 2 RTW par Blog Technique d'Audrey PETIT le il y a 17 heures et 22 minutes

- Le nouveau Gojira, c’est pour lundi… par CoqBlog le 10-11-2008, 01:18

- SharePoint : nouvel article sur la mise en place des Scopes dans MOSS Searchs par Blog Technique de Romelard Fabrice le 10-10-2008, 17:52

- Hello CS par Le Blog de julz le 10-10-2008, 12:26

- MSDN/TechNet/Microsoft Days Tour 2008 à Lille les 13 et 14 Octobre ! par RedoBlog - The .NET Gentleman !!! le 10-10-2008, 09:35

- MVC Pratique #07 - Un projet concret et le transfert des objets avec les ModelBinders par #Rui le 10-09-2008, 23:39

- SQL Server 2008 : Certifié - TS Admin (70-432) par SQL Server vu par Christian Robert le 10-09-2008, 10:58

- [WPF] Comment changer la couleur utilisée pour sélectionner les éléments d’un ItemsControl ? par Thomas Lebrun le 10-09-2008, 10:49

- Hello World! par Hamid's Place le 10-08-2008, 23:38

- SQL Profiler - Configuration pour un développeur - tracer les requêtes SQL de votre application par Atteint de JavaScriptite Aiguë [Cyril Durand] le 10-08-2008, 15:52