Database
 sql >> Baza danych >  >> RDS >> Database

Halloweenowy problem – część 2

[ Część 1 | Część 2 | Część 3 | Część 4 ]

W pierwszej części tej serii zobaczyliśmy, jak problem z Halloween dotyczy UPDATE zapytania. Krótko mówiąc, problem polegał na tym, że indeks używany do lokalizowania rekordów do aktualizacji miał zmodyfikowane klucze przez samą operację aktualizacji (kolejny dobry powód, aby używać zawartych w indeksie kolumn zamiast rozszerzania kluczy). Optymalizator zapytań wprowadził operator Eager Table Spool, aby oddzielić strony odczytu i zapisu planu wykonania, aby uniknąć problemu. W tym poście zobaczymy, jak ten sam podstawowy problem może wpłynąć na  INSERT i DELETE oświadczenia.

Wstaw oświadczenia

Teraz wiemy trochę o warunkach, które wymagają ochrony przed Halloween, dość łatwo jest utworzyć INSERT przykład, który obejmuje odczytywanie i zapisywanie kluczy o tej samej strukturze indeksu. Najprostszym przykładem jest duplikowanie wierszy w tabeli (gdzie dodanie nowych wierszy nieuchronnie modyfikuje klucze indeksu klastrowanego):

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

Problem polega na tym, że nowo wstawione wiersze mogą zostać napotkane po stronie odczytu planu wykonania, potencjalnie skutkując pętlą, która dodaje wiersze na zawsze (lub przynajmniej do osiągnięcia pewnego limitu zasobów). Optymalizator zapytań rozpoznaje to ryzyko i dodaje Chętny bufor tabeli, aby zapewnić niezbędną separację faz :

Bardziej realistyczny przykład

Prawdopodobnie nie często piszesz zapytania duplikujące każdy wiersz w tabeli, ale prawdopodobnie piszesz zapytania, w których tabela docelowa dla INSERT pojawia się również gdzieś w SELECT klauzula. Jednym z przykładów jest dodawanie wierszy z tabeli pomostowej, które jeszcze nie istnieją w miejscu docelowym:

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

Plan wykonania to:

Problem w tym przypadku jest nieco inny, choć wciąż stanowi przykład tego samego problemu podstawowego. W docelowej tabeli Demo nie ma wartości „1234”, ale tabela Staging zawiera dwa takie wpisy. Bez separacji faz pierwsza napotkana wartość „1234” zostałaby wstawiona pomyślnie, ale drugie sprawdzenie wykazałoby, że wartość „1234” już istnieje i nie będzie próbowała wstawić jej ponownie. Całość zakończyłaby się pomyślnie.

Może to przynieść pożądany rezultat w tym konkretnym przypadku (i może nawet wydawać się intuicyjnie poprawne), ale nie jest to poprawna implementacja. Standard SQL wymaga, aby zapytania modyfikujące dane wykonywały się tak, jakby trzy fazy ograniczeń odczytu, zapisu i sprawdzania zachodziły całkowicie oddzielnie (patrz część pierwsza).

Wyszukując wszystkie wiersze do wstawienia jako pojedynczą operację, powinniśmy wybrać oba wiersze „1234” z tabeli Staging, ponieważ ta wartość nie istnieje jeszcze w miejscu docelowym. Dlatego plan wykonania powinien próbować wstawić oba Wiersze „1234” z tabeli Staging, skutkujące naruszeniem klucza podstawowego:

Msg 2627, poziom 14, stan 1, wiersz 1
Naruszenie ograniczenia klucza podstawowego „PK_Demo”.
Nie można wstawić zduplikowanego klucza w obiekcie „dbo.Demo”.
Wartość zduplikowanego klucza to ( 1234).
Oświadczenie zostało zakończone.

Separacja faz zapewniana przez bufor tabeli zapewnia, że ​​wszystkie kontrole na istnienie są zakończone przed wprowadzeniem jakichkolwiek zmian w tabeli docelowej. Jeśli uruchomisz zapytanie w SQL Server z przykładowymi danymi powyżej, otrzymasz (poprawny) komunikat o błędzie.

Ochrona Halloween jest wymagana dla instrukcji INSERT, w przypadku których do tabeli docelowej odwołuje się również klauzula SELECT.

Usuń wyciągi

Możemy się spodziewać, że problem z Halloween nie dotyczy DELETE oświadczenia, ponieważ nie powinno mieć większego znaczenia, jeśli próbujemy usunąć wiersz wiele razy. Możemy zmodyfikować nasz przykład tabeli pomostowej, aby usunąć wiersze z tabeli Demo, które nie istnieją w 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
);

Ten test wydaje się potwierdzać naszą intuicję, ponieważ w planie wykonania nie ma buforu tabeli:

Ten typ DELETE nie wymaga separacji faz, ponieważ każdy wiersz ma unikalny identyfikator (identyfikator RID, jeśli tabela jest stertą, kluczami indeksu klastrowego i ewentualnie unikyfikatorem w przeciwnym razie). Ten unikalny lokalizator wierszy to stabilny klucz – nie ma mechanizmu, dzięki któremu może się to zmienić podczas realizacji tego planu, więc problem Halloween nie powstaje.

USUŃ ochronę Halloween

Niemniej jednak istnieje co najmniej jeden przypadek, w którym DELETE wymaga ochrony Halloween:gdy plan odwołuje się do wiersza w tabeli innego niż ten, który jest usuwany. Wymaga to samodzielnego łączenia, często spotykanego podczas modelowania relacji hierarchicznych. Uproszczony przykład pokazano poniżej:

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

Naprawdę powinno być zdefiniowane tutaj odwołanie do klucza obcego w tej samej tabeli, ale zignorujmy na chwilę ten projekt, który zawodzi – struktura i dane są jednak prawidłowe (i niestety często zdarza się, że klucze obce są pomijane w prawdziwym świecie). W każdym razie naszym zadaniem jest usunięcie dowolnego wiersza, w którym ref kolumna wskazuje na nieistniejący pk wartość. Naturalne DELETE zapytanie spełniające to wymaganie to:

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

Plan zapytań to:

Zauważ, że ten plan zawiera teraz kosztowną szpulę stołu Chętny. W tym przypadku wymagana jest separacja faz, ponieważ w przeciwnym razie wyniki mogą zależeć od kolejności przetwarzania wierszy:

Jeśli silnik wykonawczy zaczyna się od wiersza, w którym pk =B, nie znajdzie pasującego wiersza (ref =A i nie ma wiersza, w którym pk =A). Jeśli wykonanie przechodzi do wiersza, w którym pk =C, zostanie również usunięty, ponieważ właśnie usunęliśmy wiersz B wskazywany przez jego ref kolumna. Efektem końcowym byłoby to, że iteracyjne przetwarzanie w tej kolejności usunęłoby wszystkie wiersze z tabeli, co jest wyraźnie niepoprawne.

Z drugiej strony, jeśli silnik wykonawczy przetworzy wiersz z pk =D najpierw znajdzie pasujący wiersz (ref =C). Zakładając, że wykonanie będzie kontynuowane w odwrotnej kolejności pk kolejności, jedynym wierszem usuniętym z tabeli będzie ten, w którym pk =B. To jest poprawny wynik (pamiętaj, że zapytanie powinno zostać wykonane tak, jakby fazy odczytu, zapisu i walidacji następowały sekwencyjnie i bez nakładania się).

Rozdzielanie faz w celu walidacji ograniczeń

Na marginesie, możemy zobaczyć inny przykład separacji faz, jeśli do poprzedniego przykładu dodamy ograniczenie klucza obcego tej samej tabeli:

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

Plan wykonania WSTAWKI to:

Sama wstawka nie wymaga ochrony Halloween, ponieważ plan nie odczytuje z tej samej tabeli (źródłem danych jest wirtualna tabela w pamięci reprezentowana przez operatora Constant Scan). Standard SQL wymaga jednak, aby faza 3 (sprawdzanie ograniczeń) miała miejsce po zakończeniu fazy pisania. Z tego powodu do planu dodano separację faz Eager Table Spool po Clustered Index Index i tuż przed sprawdzeniem każdego wiersza, aby upewnić się, że ograniczenie klucza obcego pozostaje ważne.

Jeśli zaczynasz myśleć, że przetłumaczenie opartego na zbiorach deklaratywnego zapytania dotyczącego modyfikacji SQL na solidny, iteracyjny plan wykonania fizycznego jest trudną sprawą, zaczynasz rozumieć, dlaczego przetwarzanie aktualizacji (którego ochrona Halloween jest tylko bardzo małą częścią) jest najbardziej złożona część procesora zapytań.

Instrukcje DELETE wymagają ochrony Halloween, gdy występuje samodołączanie tabeli docelowej.

Podsumowanie

Ochrona Halloween może być kosztowną (ale niezbędną) funkcją w planach wykonania, która zmienia dane (gdzie „zmiana” obejmuje całą składnię SQL, która dodaje, zmienia lub usuwa wiersze). Ochrona Halloween jest wymagana do UPDATE plany, w których klucze wspólnej struktury indeksu są zarówno odczytywane, jak i modyfikowane, dla INSERT plany, w których odwołuje się do tabeli docelowej po stronie odczytu planu i dla DELETE plany, w których wykonywane jest samodzielne łączenie w tabeli docelowej.

W następnej części tej serii zostaną omówione niektóre specjalne optymalizacje problemów Halloween, które dotyczą tylko MERGE oświadczenia.

[ Część 1 | Część 2 | Część 3 | Część 4 ]


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zmiany w łącznikach danych w 2020 r.24

  2. Jak utworzyć widok w SQL

  3. Ciekawe rzeczy o wyzwalaczach ZAMIAST

  4. Jak uporządkować wiersze według sumy grup w SQL

  5. FORMAT() jest fajny i w ogóle, ale…