sql >> Databasteknik >  >> RDS >> Database

Halloweenproblemet – del 2

[ Del 1 | Del 2 | Del 3 | Del 4 ]

I den första delen av den här serien såg vi hur Halloweenproblemet gäller UPDATE frågor. För att kort sammanfatta var problemet att ett index som användes för att hitta poster som skulle uppdateras fick sina nycklar modifierade av själva uppdateringsoperationen (en annan bra anledning att använda inkluderade kolumner i ett index istället för att utöka nycklarna). Frågeoptimeraren introducerade en Eager Table Spool-operatör för att separera läs- och skrivsidorna av exekveringsplanen för att undvika problemet. I det här inlägget kommer vi att se hur samma underliggande problem kan påverka  INSERT och DELETE uttalanden.

Infoga uttalanden

Nu vet vi lite om de förhållanden som kräver Halloweenskydd, det är ganska enkelt att skapa en INSERT exempel som innebär att läsa från och skriva till nycklarna i samma indexstruktur. Det enklaste exemplet är att duplicera rader i en tabell (där att lägga till nya rader oundvikligen ändrar nycklarna i det klustrade indexet):

CREATE TABLE dbo.Demo
(
    SomeKey integer NOT NULL,
 
    CONSTRAINT PK_Demo
        PRIMARY KEY (SomeKey)
);
 
INSERT dbo.Demo
SELECT SomeKey FROM dbo.Demo;

Problemet är att nyinfogade rader kan påträffas av lässidan av exekveringsplanen, vilket potentiellt kan resultera i en loop som lägger till rader för alltid (eller åtminstone tills någon resursgräns nås). Frågeoptimeraren känner igen denna risk och lägger till en Eager Table Spool för att tillhandahålla den nödvändiga fasseparationen :

Ett mer realistiskt exempel

Du skriver förmodligen inte ofta frågor för att duplicera varje rad i en tabell, men du skriver förmodligen frågor där måltabellen för en INSERT visas också någonstans i SELECT klausul. Ett exempel är att lägga till rader från en mellanställningstabell som inte redan finns i destinationen:

CREATE TABLE dbo.Staging
(
    SomeKey integer NOT NULL
);
 
-- Sample data
INSERT dbo.Staging
    (SomeKey)
VALUES
    (1234),
    (1234);
 
-- Test query
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
);

Utförandeplanen är:

Problemet i det här fallet är subtilt annorlunda, men fortfarande ett exempel på samma kärnfråga. Det finns inget värde "1234" i måldemotabellen, men Staging-tabellen innehåller två sådana poster. Utan fasseparation skulle det första "1234"-värdet som påträffades infogas framgångsrikt, men den andra kontrollen skulle finna att värdet "1234" nu existerar och skulle inte försöka infoga det igen. Uttalandet som helhet skulle slutföras framgångsrikt.

Detta kan ge ett önskvärt resultat i det här specifika fallet (och kan till och med verka intuitivt korrekt) men det är inte en korrekt implementering. SQL-standarden kräver att datamodifieringsförfrågningar körs som om de tre faserna av läs-, skriv- och kontrollbegränsningar sker helt separat (se del ett).

När vi söker efter alla rader att infoga som en enda operation bör vi välja båda "1234"-raderna från Staging-tabellen, eftersom detta värde inte finns i målet ännu. Utförandeplanen bör därför försöka infoga båda "1234"-rader från Staging-tabellen, vilket resulterar i en primärnyckelöverträdelse:

Msg 2627, Level 14, State 1, Line 1
Brott mot PRIMARY KEY constraint 'PK_Demo'.
Det går inte att infoga dubblettnyckel i objektet 'dbo.Demo'.
Dupliceringsnyckelvärdet är ( 1234).
Utlåtandet har avslutats.

Fasseparationen som tillhandahålls av tabellspolen säkerställer att alla kontroller för existens slutförs innan några ändringar görs i måltabellen. Om du kör frågan i SQL Server med exempeldata ovan får du det (rätta) felmeddelandet.

Halloween-skydd krävs för INSERT-satser där måltabellen också hänvisas till i SELECT-satsen.

Ta bort uttalanden

Vi kan förvänta oss att Halloween-problemet inte gäller DELETE uttalanden, eftersom det inte borde spela någon roll om vi försöker ta bort en rad flera gånger. Vi kan modifiera vårt stegtabellexempel för att ta bort rader från demotabellen som inte finns i Staging:

TRUNCATE TABLE dbo.Demo;
TRUNCATE TABLE dbo.Staging;
 
INSERT dbo.Demo (SomeKey) VALUES (1234);
 
DELETE dbo.Demo
WHERE NOT EXISTS 
(
    SELECT 1 
    FROM dbo.Staging AS s 
    WHERE s.SomeKey = dbo.Demo.SomeKey
);

Det här testet verkar validera vår intuition eftersom det inte finns någon bordsspole i exekveringsplanen:

Denna typ av DELETE kräver inte fasseparation eftersom varje rad har en unik identifierare (ett RID om tabellen är en heap, klustrade indexnyckel(ar) och eventuellt en uniquiifier i övrigt). Denna unika radlokalisering är en stabil nyckel – det finns ingen mekanism genom vilken det kan ändras under genomförandet av denna plan, så Halloween-problemet uppstår inte.

RADERA Halloween-skydd

Ändå finns det åtminstone ett fall där en DELETE kräver Halloween-skydd:när planen refererar till en annan rad i tabellen än den som tas bort. Detta kräver en självkoppling, vanligen förekommande när hierarkiska relationer modelleras. Ett förenklat exempel visas nedan:

CREATE TABLE dbo.Test
(
    pk char(1) NOT NULL,
    ref char(1) NULL,
 
    CONSTRAINT PK_Test
        PRIMARY KEY (pk)
);
 
INSERT dbo.Test
    (pk, ref)
VALUES
    ('B', 'A'),
    ('C', 'B'),
    ('D', 'C');

Det borde verkligen finnas en referens för en främmande nyckel med samma tabell definierad här, men låt oss ignorera att designen misslyckas för ett ögonblick - strukturen och data är inte desto mindre giltiga (och det är tyvärr ganska vanligt att hitta främmande nycklar utelämnade i den verkliga världen). Hur som helst, uppgiften är att ta bort valfri rad där ref kolumnen pekar på en icke-existerande pk värde. Den naturliga DELETE fråga som matchar detta krav är:

DELETE dbo.Test
WHERE NOT EXISTS 
(
    SELECT 1 
    FROM dbo.Test AS t2 
    WHERE t2.pk = dbo.Test.ref
);

Frågeplanen är:

Lägg märke till att denna plan nu har en kostsam Eager Table Spool. Fasseparation krävs här eftersom resultaten annars kan bero på i vilken ordning raderna bearbetas:

Om exekveringsmotorn startar med raden där pk =B, det skulle inte hitta någon matchande rad (ref =A och det finns ingen rad där pk =A). Om körningen går vidare till raden där pk =C, den skulle också raderas eftersom vi just tagit bort rad B som pekas på av dess ref kolumn. Slutresultatet skulle bli att iterativ bearbetning i denna ordning skulle ta bort alla rader från tabellen, vilket är helt klart felaktigt.

Å andra sidan, om exekveringsmotorn bearbetade raden med pk =D först skulle den hitta en matchande rad (ref =C). Förutsatt att körningen fortsatte i omvänd pk ordning, skulle den enda raden som raderades från tabellen vara den där pk =B. Detta är det korrekta resultatet (kom ihåg att frågan ska köras som om läs-, skriv- och valideringsfaserna hade inträffat sekventiellt och utan överlappningar).

Fasseparation för begränsningsvalidering

För övrigt kan vi se ett annat exempel på fasseparation om vi lägger till en begränsning av en främmande nyckel med samma tabell till föregående exempel:

DROP TABLE dbo.Test;
 
CREATE TABLE dbo.Test
(
    pk char(1) NOT NULL,
    ref char(1) NULL,
 
    CONSTRAINT PK_Test
        PRIMARY KEY (pk),
 
    CONSTRAINT FK_ref_pk
        FOREIGN KEY (ref)
        REFERENCES dbo.Test (pk)
);
 
INSERT dbo.Test
    (pk, ref)
VALUES
    ('B', NULL),
    ('C', 'B'),
    ('D', 'C');

Utförandeplanen för INSERT är:

Själva infogningen kräver inte Halloween-skydd eftersom planen inte läser från samma tabell (datakällan är en virtuell tabell i minnet som representeras av Constant Scan-operatorn). SQL-standarden kräver dock att fas 3 (begränsningskontroll) inträffar efter att skrivfasen är klar. Av denna anledning läggs en fasseparation Eager Table Spool till planen efter det klustrade indexindexet, och precis före varje rad kontrolleras för att säkerställa att den främmande nyckelbegränsningen förblir giltig.

Om du börjar tycka att att översätta en uppsättningsbaserad deklarativ SQL-modifieringsfråga till en robust iterativ fysisk exekveringsplan är en knepig affär, börjar du se varför uppdateringsbearbetning (av vilken Halloween Protection bara är en mycket liten del) är mest komplexa delen av frågeprocessorn.

DELETE-satser kräver Halloween-skydd där en självanslutning av måltabellen finns.

Sammanfattning

Halloween Protection kan vara en dyr (men nödvändig) funktion i exekveringsplaner som ändrar data (där "ändring" inkluderar all SQL-syntax som lägger till, ändrar eller tar bort rader). Halloween-skydd krävs för UPDATE planer där en gemensam indexstrukturs nycklar både läses och modifieras, för INSERT planer där måltabellen hänvisas till på lässidan av planen och för DELETE planer där en självkoppling på måltabellen utförs.

Nästa del i den här serien kommer att täcka några speciella Halloween-problemoptimeringar som endast gäller MERGE uttalanden.

[ Del 1 | Del 2 | Del 3 | Del 4 ]


  1. ORA-00903:ogiltigt tabellnamn på PreparedStatement

  2. Installera Oracle 11g Release 2 Enterprise Edition på Windows 7 32-bitars

  3. [Video] Ansible och PostgreSQL

  4. Hur skickar man e-post från SQL Server?