Database
 sql >> Base de données >  >> RDS >> Database

UTILISER CONSEIL et DISABLE_OPTIMIZED_NESTED_LOOP

L'un des algorithmes disponibles pour joindre deux tables dans SQL Server est Nested Loops. La jointure de boucles imbriquées utilise une entrée de jointure comme table d'entrée externe et une comme table d'entrée interne. La boucle externe itère la table d'entrée externe ligne par ligne. La boucle interne, exécutée pour chaque ligne externe, recherche les lignes correspondantes dans la table d'entrée interne.

C'est ce qu'on appelle une jointure de boucles imbriquées naïves.

Si vous avez un index sur les conditions de jointure dans la table d'entrée interne, il n'est pas nécessaire d'effectuer une boucle interne pour chaque ligne de la table externe. Au lieu de cela, vous pouvez transmettre la valeur de la table externe comme argument de recherche et connecter toutes les lignes renvoyées de la table interne aux lignes de la table externe.

La recherche par la table interne est un accès aléatoire. Le SQL Server, à partir de la version 2005, a l'optimisation du tri par lots (ne pas confondre avec l'opérateur Sort en mode Batch pour les index Columnstore). Le but de l'optimisation est de commander les clés de recherche de la table externe avant d'obtenir les données de la table interne. Ainsi, un accès aléatoire sera séquentiel.

Le plan d'exécution n'affiche pas l'opération de tri par lots en tant qu'opérateur distinct. Au lieu de cela, vous pouvez voir la propriété Optimized=true dans l'opérateur Nested Loops. S'il était possible de voir le tri par lots comme un opérateur distinct dans le plan, cela ressemblerait à ceci :

Dans ce pseudo-plan, nous lisons les données de l'index ix_CustomerID non clusterisé dans l'ordre de cette clé d'index CustomerID. Ensuite, nous devons effectuer une recherche de clé dans l'index clusterisé, car ix_CustomerID n'est pas un index de couverture. Key Lookup est une opération de recherche de clé d'index en cluster - un accès aléatoire. Pour le rendre séquentiel, SQL Server peut exécuter un tri par lots par la clé d'index cluster.

Pour en savoir plus sur le tri par lots, veuillez consulter mon article Tri par lots et boucles imbriquées.

Cette optimisation donne un bon coup de pouce avec un nombre de lignes suffisant. Vous pouvez en savoir plus sur les résultats de ses tests dans le blog OPTIMIZED Nested Loops Joins, créé par Craig Freedman, un développeur d'optimiseurs.

Cependant, si le nombre réel de lignes est inférieur à celui attendu, les coûts supplémentaires du processeur pour créer ce tri peuvent masquer ses avantages, augmenter la consommation du processeur et réduire ses performances.

Considérez cet exemple particulier :

use tempdb;
go
-- create a test table (SalesOrderID - clustered PK)
create table dbo.SalesOrder(SalesOrderID int identity primary key, CustomerID int not null, SomeData char(200) not null);
go
-- add test data
with n as (select top(1000000) rn = row_number() over(order by (select null)) from sys.all_columns c1,sys.all_columns c2)
insert dbo.SalesOrder(CustomerID, SomeData) select rn%500000, str(rn,100) from n;
-- create a clustered index
create index ix_c on dbo.Salesorder(CustomerID);
go
-- the batch sort optimization is enabled by default (Nested Loops: Optimized = true)
select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000;
-- disable it with the DISABLE_OPTIMIZED_NESTED_LOOP hint (Nested Loops: Optimized = false)
select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP'));
go

Le résultat renvoyé :

Je voudrais attirer votre attention sur l'ordre différent des lignes dans la sortie. Le serveur renvoie les lignes dans l'ordre dans lequel il les traite car nous n'avons pas explicitement spécifié ORDER BY. Dans le premier cas, nous lisons progressivement à partir de l'index ix_c. Cependant, pour optimiser les lectures aléatoires à partir de l'index clusterisé, nous filtrons les lignes par la clé d'index clusterisée SalesOrderID. Dans le second cas, il n'y a pas de tri, de même que les lectures sont effectuées dans l'ordre des clés CustomerID de l'index non clusterisé ix_c.

Différence avec l'indicateur de trace 2340

Malgré le fait que la documentation spécifie l'indicateur de trace 2340 comme équivalent de l'indicateur DISABLE_OPTIMIZED_NESTED_LOOP, ce n'est pas réellement vrai.

Considérez l'exemple suivant où j'utiliserai la commande non documentée UPDATE STATISTICS … WITH PAGECOUNT pour tromper l'optimiseur en disant que la table prend plus de pages qu'elle n'en a réellement. Ensuite, jetez un œil à ces requêtes :

  1. Sans aucun indice (j'ai ajouté MAXDOP pour garder un plan simple) ;
  2. Avec l'indice DISABLE_OPTIMIZED_NESTED_LOOP ;
  3. Avec l'indicateur de trace 2340.
-- create a huge table
update statistics dbo.SalesOrder with pagecount = 100000;
go
set showplan_xml on;
go
-- 1. without hints
select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(maxdop 1);
-- 2. hint
select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP'), maxdop 1);
-- 3. trace flag
select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(querytraceon 2340, maxdop 1);
go
set showplan_xml off;
go

En conséquence, nous avons les plans suivants :

Nested Loops a la propriété Optimized =false dans les trois plans. Le fait est qu'en augmentant la largeur du tableau, on augmente également le coût d'accès aux données. Lorsque le coût est suffisamment élevé, SQL Server peut utiliser l'opérateur de tri explicite plutôt que l'opérateur de tri par lots implicite. Nous pouvons le voir dans le premier plan de requête.

Dans la deuxième requête, nous avons utilisé l'indicateur DISABLE_OPTIMIZED_NESTED_LOOP qui désactive le tri par lots implicite. Cependant, cela supprime un tri explicite par un opérateur distinct.

Dans le troisième plan, nous pouvons voir que malgré l'ajout de l'indicateur de trace 2340, l'opérateur de tri existe.

Ainsi, la différence entre le conseil et le drapeau est la suivante :un conseil désactive l'optimisation en transformant un accès aléatoire en un accès série selon que le serveur l'implémente avec le tri implicite par lots ou avec l'opérateur Sort séparé.

PS Les plans peuvent dépendre de l'équipement. Ainsi, si vous ne parvenez pas à exécuter ces requêtes, essayez d'augmenter ou de diminuer la taille de la colonne SomeData char(200) dans la description de la table dbo.SalesOrder.

L'article a été traduit par l'équipe de Codingsight avec la permission de l'auteur.