sql >> Database >  >> RDS >> Database

Het Halloween-probleem - Deel 3

[ Deel 1 | Deel 2 | Deel 3 | Deel 4 ]

De MERGE statement (geïntroduceerd in SQL Server 2008) stelt ons in staat om een ​​combinatie van INSERT . uit te voeren , UPDATE , en DELETE bewerkingen met een enkele verklaring. De Halloween-beveiligingsproblemen voor MERGE zijn meestal een combinatie van de vereisten van de individuele bewerkingen, maar er zijn enkele belangrijke verschillen en een aantal interessante optimalisaties die alleen van toepassing zijn op MERGE .

Het Halloween-probleem vermijden met MERGE

We beginnen opnieuw met het voorbeeld van Demo en Staging uit deel twee:

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
);

Zoals u zich wellicht herinnert, werd dit voorbeeld gebruikt om aan te tonen dat een INSERT vereist Halloween-bescherming wanneer er ook naar de invoegdoeltabel wordt verwezen in de SELECT deel van de zoekopdracht (de EXISTS clausule in dit geval). Het juiste gedrag voor de INSERT bovenstaande verklaring is om te proberen beide . toe te voegen 1234 waarden, en als gevolg daarvan mislukken met een PRIMARY KEY overtreding. Zonder fasescheiding, de INSERT zou ten onrechte één waarde toevoegen, zonder dat er een fout werd gegenereerd.

Het INSERT-uitvoeringsplan

De bovenstaande code heeft één verschil met die in deel twee; een niet-geclusterde index op de Staging-tabel is toegevoegd. De INSERT uitvoeringsplan nog vereist echter Halloween-bescherming:

Het MERGE-uitvoeringsplan

Probeer nu dezelfde logische invoeging uitgedrukt met MERGE syntaxis:

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);

Als u niet bekend bent met de syntaxis, is de logica die er is om rijen in de Staging- en Demo-tabellen te vergelijken op de SomeKey-waarde, en als er geen overeenkomende rij wordt gevonden in de doeltabel (Demo), voegen we een nieuwe rij in. Dit heeft precies dezelfde semantiek als de vorige INSERT...WHERE NOT EXISTS code natuurlijk. Het uitvoeringsplan is echter heel anders:

Let op het ontbreken van een Eager Table Spool in dit plan. Desondanks geeft de query nog steeds de juiste foutmelding. Het lijkt erop dat SQL Server een manier heeft gevonden om de MERGE . uit te voeren plan iteratief met inachtneming van de logische fasescheiding vereist door de SQL-standaard.

De gatenvullende optimalisatie

In de juiste omstandigheden kan de SQL Server-optimizer herkennen dat de MERGE statement is vullend , wat gewoon een andere manier is om te zeggen dat de instructie alleen rijen toevoegt waar er een bestaand gat is in de sleutel van de doeltabel.

Om deze optimalisatie toe te passen, worden de waarden gebruikt in de WHEN NOT MATCHED BY TARGET clausule moet precies overeenkomen met de ON onderdeel van de USING clausule. De doeltabel moet ook een unieke sleutel hebben (een vereiste waaraan wordt voldaan door de PRIMARY KEY in het onderhavige geval). Waar aan deze vereisten wordt voldaan, wordt de MERGE verklaring vereist geen bescherming tegen het Halloween-probleem.

Natuurlijk, de MERGE statement is logisch niet meer of minder gatvullend dan de originele INSERT...WHERE NOT EXISTS syntaxis. Het verschil is dat de optimizer volledige controle heeft over het implementeren van de MERGE statement, terwijl de INSERT syntaxis zou vereisen dat het redeneert over de bredere semantiek van de query. Een mens kan gemakkelijk zien dat de INSERT is ook gatenvullend, maar de optimizer denkt niet op dezelfde manier over dingen als wij.

Ter illustratie van de exacte overeenkomst vereiste die ik noemde, overweeg dan de volgende query-syntaxis, die niet . doet profiteren van de gatvullende optimalisatie. Het resultaat is volledige Halloween-bescherming door een Eager Table Spool:

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);

Het enige verschil is de vermenigvuldiging met één in de VALUES clausule - iets dat de logica van de zoekopdracht niet verandert, maar dat voldoende is om te voorkomen dat de gatenvullende optimalisatie wordt toegepast.

Gaten vullen met geneste lussen

In het vorige voorbeeld koos de optimizer ervoor om de tabellen samen te voegen met behulp van een Merge-join. De gatenvullende optimalisatie kan ook worden toegepast waar een Nested Loops-join is gekozen, maar dit vereist een extra uniciteitsgarantie op de brontabel en een indexzoekopdracht aan de binnenkant van de join. Om dit in actie te zien, kunnen we de bestaande staginggegevens wissen, uniciteit toevoegen aan de niet-geclusterde index en de MERGE proberen nogmaals:

-- 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);

Het resulterende uitvoeringsplan gebruikt opnieuw de gatenvullende optimalisatie om Halloween-bescherming te vermijden, met behulp van een geneste lus-join en een innerlijke zoekactie in de doeltabel:

Onnodige indexdoorgangen vermijden

Waar de gatenvullende optimalisatie van toepassing is, kan de motor ook een verdere optimalisatie toepassen. Het kan de huidige indexpositie onthouden tijdens lezing de doeltabel (één rij tegelijk verwerken, onthoud) en hergebruik die informatie bij het uitvoeren van de invoeging, in plaats van door de b-boom te zoeken om de invoeglocatie te vinden. De redenering is dat de huidige leespositie zeer waarschijnlijk op dezelfde pagina staat waar de nieuwe rij moet worden ingevoegd. Controleren of de rij inderdaad op deze pagina thuishoort, gaat erg snel, omdat het alleen de laagste en hoogste toetsen betreft die daar momenteel zijn opgeslagen.

De combinatie van het elimineren van de Eager Table Spool en het opslaan van een indexnavigatie per rij kan een aanzienlijk voordeel opleveren bij OLTP-workloads, op voorwaarde dat het uitvoeringsplan wordt opgehaald uit de cache. De compilatiekosten voor MERGE instructies is eerder hoger dan voor INSERT , UPDATE en DELETE , dus hergebruik van plannen is een belangrijke overweging. Het is ook handig om ervoor te zorgen dat pagina's voldoende vrije ruimte hebben voor nieuwe rijen, om paginasplitsingen te voorkomen. Dit wordt meestal bereikt door normaal indexonderhoud en de toewijzing van een geschikte FILLFACTOR .

Ik noem OLTP-workloads, die doorgaans een groot aantal relatief kleine wijzigingen bevatten, omdat de MERGE optimalisaties zijn mogelijk geen goede keuze wanneer een groot aantal rijen per instructie wordt verwerkt. Andere optimalisaties zoals minimaal gelogde INSERTs kan momenteel niet worden gecombineerd met het vullen van gaten. Zoals altijd moeten de prestatiekenmerken worden gebenchmarkt om ervoor te zorgen dat de verwachte voordelen worden gerealiseerd.

De gatenvullende optimalisatie voor MERGE invoegingen kunnen worden gecombineerd met updates en verwijderingen met behulp van extra MERGE clausules; elke gegevensveranderende operatie wordt afzonderlijk beoordeeld voor het Halloween-probleem.

De deelname vermijden

De uiteindelijke optimalisatie die we zullen bekijken, kan worden toegepast waar de MERGE statement bevat update- en delete-bewerkingen, evenals een gatvullende insert, en de doeltabel heeft een unieke geclusterde index. Het volgende voorbeeld toont een algemene MERGE patroon waarbij niet-overeenkomende rijen worden ingevoegd en overeenkomende rijen worden bijgewerkt of verwijderd, afhankelijk van een aanvullende voorwaarde:

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);

De MERGE verklaring die nodig is om alle vereiste wijzigingen aan te brengen, is opmerkelijk compact:

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;

Het uitvoeringsplan is nogal verrassend:

Geen Halloween-bescherming, geen verbinding tussen de bron- en doeltabellen, en het komt niet vaak voor dat u een operator voor geclusterde index invoegen ziet, gevolgd door een geclusterde index samenvoegen naar dezelfde tabel. Dit is een andere optimalisatie gericht op OLTP-workloads met veel hergebruik van plannen en geschikte indexering.

Het idee is om een ​​rij uit de brontabel te lezen en deze onmiddellijk in het doel in te voegen. Als een toetsovertreding het gevolg is, wordt de fout onderdrukt, de operator Invoegen voert de conflicterende rij uit die hij heeft gevonden en die rij wordt vervolgens verwerkt voor een update- of verwijderingsbewerking met behulp van de operator Plan samenvoegen zoals normaal.

Als de oorspronkelijke invoeging slaagt (zonder een sleutelovertreding), gaat de verwerking verder met de volgende rij van de bron (de operator Samenvoegen verwerkt alleen updates en verwijderingen). Deze optimalisatie komt voornamelijk ten goede aan MERGE query's waarbij de meeste bronrijen resulteren in een insert. Nogmaals, zorgvuldige benchmarking is vereist om ervoor te zorgen dat de prestaties beter zijn dan het gebruik van afzonderlijke verklaringen.

Samenvatting

De MERGE statement biedt verschillende unieke optimalisatiemogelijkheden. In de juiste omstandigheden kan het de noodzaak vermijden om expliciete Halloween-bescherming toe te voegen in vergelijking met een gelijkwaardige INSERT bewerking, of misschien zelfs een combinatie van INSERT , UPDATE , en DELETE verklaringen. Extra MERGE -specifieke optimalisaties kunnen de index b-tree traversal vermijden die gewoonlijk nodig is om de invoegpositie voor een nieuwe rij te lokaliseren, en kunnen ook de noodzaak vermijden om de bron- en doeltabellen volledig samen te voegen.

In het laatste deel van deze serie zullen we bekijken hoe de query-optimizer redeneert over de noodzaak van Halloween-bescherming, en wat meer trucs identificeren die het kan gebruiken om te voorkomen dat Eager Table Spools moet worden toegevoegd aan uitvoeringsplannen die gegevens wijzigen.

[ Deel 1 | Deel 2 | Deel 3 | Deel 4 ]


  1. PostgreSQL accepteert geen kolomalias in WHERE-clausule

  2. MySQL-opdrachten:spiekbriefje met veelvoorkomende MySQL-query's

  3. SQLite INTERSECT-operator

  4. MariaDB JSON_VALUE() uitgelegd