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

Le problème d'Halloween - Partie 3

[ Partie 1 | Partie 2 | Partie 3 | Partie 4 ]

Le MERGE (introduite dans SQL Server 2008) nous permet d'effectuer un mélange de INSERTs , UPDATE , et DELETE opérations à l'aide d'une seule instruction. Les problèmes de protection d'Halloween pour MERGE sont principalement une combinaison des exigences des opérations individuelles, mais il existe quelques différences importantes et quelques optimisations intéressantes qui ne s'appliquent qu'à MERGE .

Éviter le problème d'Halloween avec MERGE

Nous commençons par examiner à nouveau l'exemple de démonstration et de mise en scène de la deuxième partie :

CREATE TABLE dbo.Demo
(
    SomeKey integer NOT NULL,
 
    CONSTRAINT PK_Demo
        PRIMARY KEY (SomeKey)
);
 
CREATE TABLE dbo.Staging
(
    SomeKey integer NOT NULL
);
 
INSERT dbo.Staging
    (SomeKey)
VALUES
    (1234),
    (1234);
 
CREATE NONCLUSTERED INDEX c 
ON dbo.Staging (SomeKey);
 
INSERT dbo.Demo
SELECT s.SomeKey
FROM dbo.Staging AS s
WHERE NOT EXISTS
(
    SELECT 1
    FROM dbo.Demo AS d
    WHERE d.SomeKey = s.SomeKey
);

Comme vous vous en souvenez peut-être, cet exemple a été utilisé pour montrer qu'un INSERTs nécessite la protection Halloween lorsque la table cible d'insertion est également référencée dans le SELECT partie de la requête (le EXISTS clause dans ce cas). Le comportement correct pour le INSERTs la déclaration ci-dessus est d'essayer d'ajouter les deux 1234 valeurs, et par conséquent échouer avec une PRIMARY KEY violation. Sans séparation de phases, le INSERTs ajouterait incorrectement une valeur, se terminant sans qu'une erreur ne soit renvoyée.

Le plan d'exécution INSERT

Le code ci-dessus présente une différence par rapport à celui utilisé dans la deuxième partie ; un index non clusterisé sur la table Staging a été ajouté. Le INSERTs plan d'exécution toujours nécessite cependant la protection d'Halloween :

Le plan d'exécution MERGE

Essayez maintenant la même insertion logique exprimée en utilisant MERGE syntaxe :

MERGE dbo.Demo AS d
USING dbo.Staging AS s ON
    s.SomeKey = d.SomeKey
WHEN NOT MATCHED BY TARGET THEN
    INSERT (SomeKey)
    VALUES (s.SomeKey);

Si vous n'êtes pas familier avec la syntaxe, la logique consiste à comparer les lignes des tables Staging et Demo sur la valeur SomeKey, et si aucune ligne correspondante n'est trouvée dans la table cible (Demo), nous insérons une nouvelle ligne. Cela a exactement la même sémantique que le précédent INSERT...WHERE NOT EXISTS coder, bien sûr. Le plan d'exécution est cependant assez différent :

Remarquez l'absence d'une bobine de table Eager dans ce plan. Malgré cela, la requête produit toujours le message d'erreur correct. Il semble que SQL Server ait trouvé un moyen d'exécuter le MERGE planifier de manière itérative en respectant la séparation logique des phases exigée par la norme SQL.

L'optimisation du remplissage des trous

Dans les bonnes circonstances, l'optimiseur SQL Server peut reconnaître que le MERGE l'énoncé est plein de trous , ce qui est juste une autre façon de dire que l'instruction n'ajoute que des lignes là où il existe un écart existant dans la clé de la table cible.

Pour que cette optimisation soit appliquée, les valeurs utilisées dans le WHEN NOT MATCHED BY TARGET la clause doit exactement correspondre au ON partie de USING clause. De plus, la table cible doit avoir une clé unique (une exigence satisfaite par la PRIMARY KEY dans le cas présent). Lorsque ces exigences sont remplies, le MERGE déclaration ne nécessite pas de protection contre le problème d'Halloween.

Bien sûr, le MERGE l'instruction est logique pas plus ou moins de remplissage de trous que l'original INSERT...WHERE NOT EXISTS syntaxe. La différence est que l'optimiseur a un contrôle total sur la mise en œuvre de la MERGE alors que l'instruction INSERTs la syntaxe l'obligerait à raisonner sur la sémantique plus large de la requête. Un humain peut facilement voir que le INSERTs remplit également les trous, mais l'optimiseur ne pense pas les choses de la même manière que nous.

Pour illustrer la correspondance exacte exigence que j'ai mentionnée, considérez la syntaxe de requête suivante, qui ne le fait pas bénéficier de l'optimisation du remplissage des trous. Le résultat est une protection Halloween complète fournie par une bobine de table Eager :

MERGE dbo.Demo AS d
USING dbo.Staging AS s ON
    s.SomeKey = d.SomeKey
WHEN NOT MATCHED THEN
    INSERT (SomeKey)
    VALUES (s.SomeKey * 1);

La seule différence est la multiplication par un dans les VALUES clause - quelque chose qui ne change pas la logique de la requête, mais qui est suffisant pour empêcher l'application de l'optimisation de remplissage des trous.

Remplissage de trous avec des boucles imbriquées

Dans l'exemple précédent, l'optimiseur a choisi de joindre les tables à l'aide d'une jointure par fusion. L'optimisation du remplissage des trous peut également être appliquée lorsqu'une jointure Nested Loops est choisie, mais cela nécessite une garantie d'unicité supplémentaire sur la table source et une recherche d'index sur le côté interne de la jointure. Pour voir cela en action, nous pouvons effacer les données de staging existantes, ajouter un caractère unique à l'index non clusterisé et essayer le MERGE encore :

-- Remove existing duplicate rows
TRUNCATE TABLE dbo.Staging;
 
-- Convert index to unique
CREATE UNIQUE NONCLUSTERED INDEX c 
ON dbo.Staging (SomeKey)
WITH (DROP_EXISTING = ON);
 
-- Sample data
INSERT dbo.Staging
    (SomeKey)
VALUES
    (1234),
    (5678);
 
-- Hole-filling merge
MERGE dbo.Demo AS d
USING dbo.Staging AS s ON
    s.SomeKey = d.SomeKey
WHEN NOT MATCHED THEN
    INSERT (SomeKey)
    VALUES (s.SomeKey);

Le plan d'exécution résultant utilise à nouveau l'optimisation du remplissage des trous pour éviter la protection d'Halloween, en utilisant une jointure de boucles imbriquées et une recherche interne dans la table cible :

Éviter les parcours d'index inutiles

Lorsque l'optimisation de remplissage de trous s'applique, le moteur peut également appliquer une autre optimisation. Il peut se souvenir de la position actuelle de l'index lors de la lecture la table cible (traitant une ligne à la fois, rappelez-vous) et réutilisez ces informations lors de l'insertion, au lieu de rechercher l'emplacement de l'insertion dans le b-tree. Le raisonnement est que la position de lecture actuelle est très susceptible d'être sur la même page où la nouvelle ligne doit être insérée. Vérifier que la ligne appartient bien à cette page est très rapide, puisqu'il s'agit de ne vérifier que les clés les plus basses et les plus hautes qui y sont actuellement stockées.

La combinaison de l'élimination du spool de table Eager et de l'enregistrement d'une navigation d'index par ligne peut offrir un avantage significatif dans les charges de travail OLTP, à condition que le plan d'exécution soit extrait du cache. Le coût de compilation pour MERGE instructions est plutôt plus élevé que pour INSERTs , UPDATE et DELETE , la réutilisation du plan est donc une considération importante. Il est également utile de s'assurer que les pages disposent de suffisamment d'espace libre pour accueillir de nouvelles lignes, en évitant les fractionnements de page. Ceci est généralement réalisé grâce à la maintenance normale de l'index et à l'attribution d'un FILLFACTOR approprié .

Je mentionne les charges de travail OLTP, qui comportent généralement un grand nombre de changements relativement petits, car le MERGE les optimisations peuvent ne pas être un bon choix lorsqu'un grand nombre de lignes sont traitées par instruction. D'autres optimisations comme les INSERTs à journalisation minimale ne peut actuellement pas être combiné avec le remplissage des trous. Comme toujours, les caractéristiques de performance doivent être comparées pour s'assurer que les avantages attendus sont réalisés.

L'optimisation du remplissage des trous pour MERGE les insertions peuvent être combinées avec des mises à jour et des suppressions à l'aide de MERGE supplémentaires clauses ; chaque opération de modification de données est évaluée séparément pour le problème d'Halloween.

Éviter la jointure

L'optimisation finale que nous examinerons peut être appliquée là où le MERGE L'instruction contient des opérations de mise à jour et de suppression ainsi qu'une insertion de remplissage de trous, et la table cible a un index clusterisé unique. L'exemple suivant montre un MERGE commun modèle dans lequel les lignes sans correspondance sont insérées et les lignes correspondantes sont mises à jour ou supprimées en fonction d'une condition supplémentaire :

CREATE TABLE #T
(
    col1 integer NOT NULL,
    col2 integer NOT NULL,
 
    CONSTRAINT PK_T
        PRIMARY KEY (col1)
);
 
CREATE TABLE #S
(
    col1 integer NOT NULL,
    col2 integer NOT NULL,
 
    CONSTRAINT PK_S
        PRIMARY KEY (col1)
);
 
INSERT #T
    (col1, col2)
VALUES
    (1, 50),
    (3, 90);
 
INSERT #S
    (col1, col2)
VALUES
    (1, 40),
    (2, 80),
    (3, 90);

Le MERGE l'instruction requise pour effectuer toutes les modifications requises est remarquablement compacte :

MERGE #T AS t
USING #S AS s ON t.col1 = s.col1
WHEN NOT MATCHED THEN INSERT VALUES (s.col1, s.col2)
WHEN MATCHED AND t.col2 - s.col2 = 0 THEN DELETE
WHEN MATCHED THEN UPDATE SET t.col2 -= s.col2;

Le plan d'exécution est assez surprenant :

Pas de protection Halloween, pas de jointure entre les tables source et cible, et ce n'est pas souvent que vous verrez un opérateur d'insertion d'index clusterisé suivi d'une fusion d'index clusterisé vers la même table. Il s'agit d'une autre optimisation ciblée sur les charges de travail OLTP avec une réutilisation élevée des plans et une indexation appropriée.

L'idée est de lire une ligne de la table source et d'essayer immédiatement de l'insérer dans la cible. Si une violation de clé se produit, l'erreur est supprimée, l'opérateur d'insertion génère la ligne en conflit qu'il a trouvée, et cette ligne est ensuite traitée pour une opération de mise à jour ou de suppression à l'aide de l'opérateur de plan de fusion comme d'habitude.

Si l'insertion d'origine réussit (sans violation de clé), le traitement se poursuit avec la ligne suivante à partir de la source (l'opérateur Merge traite uniquement les mises à jour et les suppressions). Cette optimisation profite principalement à MERGE les requêtes où la plupart des lignes source aboutissent à une insertion. Encore une fois, une analyse comparative minutieuse est nécessaire pour s'assurer que les performances sont meilleures que l'utilisation d'instructions séparées.

Résumé

Le MERGE offre plusieurs possibilités d'optimisation uniques. Dans les bonnes circonstances, cela peut éviter d'avoir à ajouter une protection Halloween explicite par rapport à un équivalent INSERTs opération, ou peut-être même une combinaison de INSERTs , UPDATE , et DELETE déclarations. MERGE supplémentaire -des optimisations spécifiques peuvent éviter la traversée de l'index b-tree qui est généralement nécessaire pour localiser la position d'insertion d'une nouvelle ligne, et peut également éviter d'avoir à joindre complètement les tables source et cible.

Dans la dernière partie de cette série, nous examinerons comment l'optimiseur de requête raisonne sur la nécessité d'une protection Halloween et identifierons d'autres astuces qu'il peut utiliser pour éviter d'avoir à ajouter des spools de table impatients aux plans d'exécution qui modifient les données.

[ Partie 1 | Partie 2 | Partie 3 | Partie 4 ]