sql >> Database teknologi >  >> RDS >> Database

Halloween-problemet – del 2

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

I den første del af denne serie så vi, hvordan Halloween-problemet gælder for UPDATE forespørgsler. For kort at opsummere var problemet, at et indeks, der blev brugt til at lokalisere poster, der skulle opdateres, fik sine nøgler ændret af selve opdateringsoperationen (en anden god grund til at bruge inkluderede kolonner i et indeks i stedet for at udvide nøglerne). Forespørgselsoptimeringsværktøjet introducerede en Eager Table Spool-operatør for at adskille læse- og skrivesiderne af udførelsesplanen for at undgå problemet. I dette indlæg vil vi se, hvordan det samme underliggende problem kan påvirke  INSERT og DELETE udsagn.

Indsæt erklæringer

Nu ved vi lidt om de forhold, der kræver Halloween-beskyttelse, det er ret nemt at oprette en INSERT eksempel, der involverer læsning fra og skrivning til tasterne i den samme indeksstruktur. Det enkleste eksempel er at duplikere rækker i en tabel (hvor tilføjelse af nye rækker uundgåeligt ændrer nøglerne til det klyngede indeks):

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

Problemet er, at nyindsatte rækker kan blive stødt på af læsesiden af ​​udførelsesplanen, hvilket potentielt kan resultere i en løkke, der tilføjer rækker for altid (eller i det mindste indtil en ressourcegrænse er nået). Forespørgselsoptimeringsværktøjet genkender denne risiko og tilføjer en Eager Table Spool for at give den nødvendige faseadskillelse :

Et mere realistisk eksempel

Du skriver sandsynligvis ikke ofte forespørgsler for at duplikere hver række i en tabel, men du skriver sandsynligvis forespørgsler, hvor måltabellen for en INSERT vises også et sted i SELECT klausul. Et eksempel er tilføjelse af rækker fra en iscenesættelsestabel, som ikke allerede findes 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
);

Udførelsesplanen er:

Problemet i dette tilfælde er subtilt anderledes, men stadig et eksempel på det samme kerneproblem. Der er ingen værdi '1234' i måldemotabellen, men Staging-tabellen indeholder to sådanne poster. Uden faseadskillelse ville den første '1234'-værdi, der stødes på, blive indsat med succes, men den anden kontrol ville finde ud af, at værdien '1234' nu eksisterer og ikke ville forsøge at indsætte den igen. Redegørelsen som helhed ville fuldføres med succes.

Dette kan give et ønskeligt resultat i dette særlige tilfælde (og kan endda virke intuitivt korrekt), men det er ikke en korrekt implementering. SQL-standarden kræver, at datamodifikationsforespørgsler udføres, som om de tre faser af læse-, skrive- og kontrolbegrænsninger forekommer helt adskilt (se første del).

Når vi søger efter alle rækker, der skal indsættes som en enkelt operation, bør vi vælge begge '1234' rækker fra Staging-tabellen, da denne værdi endnu ikke findes i målet. Udførelsesplanen bør derfor forsøge at indsætte begge '1234' rækker fra iscenesættelsestabellen, hvilket resulterer i en primærnøgleovertrædelse:

Msg 2627, Level 14, State 1, Line 1
Overtrædelse af PRIMÆR NØGLE-begrænsning 'PK_Demo'.
Kan ikke indsætte dubletnøgle i objekt 'dbo.Demo'.
Duplikatnøgleværdien er ( 1234).
Opgørelsen er blevet afsluttet.

Faseadskillelsen leveret af tabelspolen sikrer, at alle kontroller for eksistens er gennemført, før der foretages ændringer i måltabellen. Hvis du kører forespørgslen i SQL Server med eksempeldataene ovenfor, vil du modtage den (korrekte) fejlmeddelelse.

Halloween-beskyttelse er påkrævet for INSERT-sætninger, hvor måltabellen også refereres til i SELECT-sætningen.

Slet erklæringer

Vi forventer måske, at Halloween-problemet ikke gælder for DELETE udsagn, da det ikke burde være lige meget, hvis vi forsøger at slette en række flere gange. Vi kan ændre vores eksempel på opstillingstabel for at fjerne rækker fra Demo-tabellen, der ikke findes 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
);

Denne test ser ud til at validere vores intuition, fordi der ikke er nogen bordspole i udførelsesplanen:

Denne type DELETE kræver ikke faseadskillelse, fordi hver række har et unikt id (et RID, hvis tabellen er en heap, klyngede indeksnøgler og muligvis en uniquiifier ellers). Denne unikke rækkefinder er en stabil nøgle – der er ingen mekanisme, hvormed det kan ændre sig under udførelsen af ​​denne plan, så Halloween-problemet opstår ikke.

SLET Halloween-beskyttelse

Ikke desto mindre er der mindst ét ​​tilfælde, hvor en DELETE kræver Halloween-beskyttelse:når planen refererer til en anden række i tabellen end den, der slettes. Dette kræver en selvforbindelse, som ofte findes, når hierarkiske relationer modelleres. Et forenklet eksempel er vist nedenfor:

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

Der burde virkelig være defineret en fremmednøglereference med samme tabel her, men lad os ignorere, at designet fejler et øjeblik - strukturen og dataene er ikke desto mindre gyldige (og det er desværre ret almindeligt at finde fremmednøgler udeladt i den virkelige verden). Under alle omstændigheder er opgaven at slette enhver række, hvor ref kolonne peger på en ikke-eksisterende pk værdi. Den naturlige DELETE forespørgsel, der matcher dette krav, er:

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

Forespørgselsplanen er:

Bemærk, at denne plan nu har en kostbar Ivrig bordspole. Faseadskillelse er påkrævet her, fordi resultaterne ellers kan afhænge af rækkefølgen, som rækker behandles i:

Hvis udførelsesmotoren starter med rækken hvor pk =B, den ville ikke finde nogen matchende række (ref =A og der er ingen række hvor pk =A). Hvis eksekveringen går videre til rækken hvor pk =C, ville den også blive slettet, fordi vi lige har fjernet række B, der peges på af dens ref kolonne. Slutresultatet ville være, at iterativ behandling i denne rækkefølge ville slette alle rækkerne fra tabellen, hvilket klart er forkert.

På den anden side, hvis udførelsesmotoren behandlede rækken med pk =D først ville den finde en matchende række (ref =C). Forudsat at udførelsen fortsatte i omvendt pk rækkefølge, ville den eneste række, der blev slettet fra tabellen, være den, hvor pk =B. Dette er det korrekte resultat (husk, at forespørgslen skal udføres, som om læse-, skrive- og valideringsfaserne var foregået sekventielt og uden overlap).

Faseadskillelse for begrænsningsvalidering

Som en sidebemærkning kan vi se et andet eksempel på faseadskillelse, hvis vi tilføjer en fremmednøgle-begrænsning med samme tabel til det foregående eksempel:

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

Udførelsesplanen for INSERT er:

Indlægget i sig selv kræver ikke Halloween-beskyttelse, da planen ikke læser fra den samme tabel (datakilden er en virtuel tabel i hukommelsen repræsenteret af Constant Scan-operatoren). SQL-standarden kræver dog, at fase 3 (kontrol af begrænsninger) finder sted, efter at skrivefasen er afsluttet. Af denne grund tilføjes en faseadskillelse Eager Table Spool til planen efter det Clustered Index Index, og lige før hver række kontrolleres for at sikre, at den fremmede nøgle-begrænsning forbliver gyldig.

Hvis du begynder at tro, at oversættelse af en sæt-baseret deklarativ SQL-modifikationsforespørgsel til en robust iterativ fysisk udførelsesplan er en vanskelig forretning, begynder du at se, hvorfor opdateringsbehandling (hvoraf Halloween Protection kun er en meget lille del) er mest komplekse del af forespørgselsprocessoren.

DELETE-udsagn kræver Halloween-beskyttelse, hvor en selvtilslutning af måltabellen er til stede.

Oversigt

Halloween Protection kan være en dyr (men nødvendig) funktion i eksekveringsplaner, der ændrer data (hvor 'ændring' inkluderer al SQL-syntaks, der tilføjer, ændrer eller fjerner rækker). Halloween-beskyttelse er påkrævet for UPDATE planer, hvor en fælles indeksstrukturs nøgler både læses og ændres, for INSERT planer, hvor der henvises til måltabellen på læsesiden af ​​planen, og for DELETE planer, hvor der udføres en selv-join på måltabellen.

Den næste del i denne serie vil dække nogle specielle Halloween-problemoptimeringer, der kun gælder for MERGE udsagn.

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


  1. Hvordan forespørger jeg mellem to datoer ved hjælp af MySQL?

  2. Anonymiser dine planoplysninger indbygget i Plan Explorer

  3. MySQL:hvordan laver man sikkerhed på rækkeniveau (som Oracles Virtual Private Database)?

  4. SQL UPDATE Syntaks – Listet af DBMS