14

Mam problemy z wydajnością SQL. W nagłym przypadku następujące zapytania są bardzo powolne:Bardzo powolne zapytanie DELETE

Mam dwie listy zawierające identyfikatory pewnej tabeli. Muszę usunąć wszystkie rekordy z pierwszej listy, jeśli już istnieje Id w drugim liście:

DECLARE @IdList1 TABLE(Id INT) 
DECLARE @IdList2 TABLE(Id INT) 

-- Approach 1 
DELETE list1 
FROM @IdList1 list1 
INNER JOIN @IdList2 list2 ON list1.Id = list2.Id 

-- Approach 2 
DELETE FROM @IdList1 
WHERE Id IN (SELECT Id FROM @IdList2) 

Jest możliwe, że dwie listy zawiera ponad 10.000 rekordów. W takim przypadku oba zapytania trwają dłużej niż 20 sekund.

Plan wykonania pokazał także coś, czego nie rozumiem. Może to wyjaśnia, dlaczego jest tak powolny: Queryplan of both queries

I Wypełniłem obie listy z 10.000 kolejnych liczb całkowitych, więc obie listy zawierały wartość 1-10.000 jako punkt początkowy.

Jak widać oba zapytania są wyświetlane dla @ IdList2 Rzeczywista liczba wierszy wynosi 50.005.000 !!. @ IdList1 jest poprawny (Rzeczywista liczba wierszy jest 10.000)

Wiem, że istnieją inne rozwiązania, jak rozwiązać ten problem. Tak jak w przypadku wypełniania trzeciej listy usuniętej z pierwszej listy. Ale moje pytanie brzmi:

Dlaczego są one tak powolne i dlaczego widzę te dziwne plany zapytań?

+0

Czy jest to problem, który można napotkać w realnym scenariuszu lub tylko w tej specilizowanej sytuacji? – Jodrell

+1

@Jodrell - Podstawowe problemy związane z brakiem statystyk rekompilacji zmiennych tabel (i brak przydatnych indeksów na nich) są bardzo powszechne. –

Odpowiedz

14

dodać klucz podstawowy do zmiennych stole i oglądać je krzyczeć

DECLARE @IdList1 TABLE(Id INT primary Key not null) 
DECLARE @IdList2 TABLE(Id INT primary Key not null) 

bo nie ma indeks na tych zmiennych stołowych, każdy przystępuje lub podzapytania musi zbadać rzędu 10.000 razy 10000 = 100.000.000 par wartości.

+0

Czy pomocny jest indeks na '@ IdList1'? – Jodrell

+2

"Wszelkie połączenia lub podkwerendy muszą zostać przeanalizowane w kolejności 10.000 razy 10.000 = 100 000 000 par wartości." dotyczy to tylko zagnieżdżonych pętli. Łączenie hash lub scalania przetwarzałoby każde wejście jeden raz (chociaż sprzężenie było również potrzebne) –

+1

@martin, Nie czytałem tych rzeczy przez jakiś czas, więc zapomniałem reguł, ale czy to nie jest wybór zagnieżdżonych pętle, ponieważ nie ma indeksu? Aby wykonać inne algorytmy pętli, nie potrzeba indeksu do sortowania wartości? Ponadto, bez indeksu, wciąż musi zbadać każdą parę wartości - bez względu na to, jaki algorytm pętli wykorzystuje do ich utworzenia. - wyjątek stanowi, jak sam to zauważyłeś, scalenie, ale musi je poprzedzić. –

12

Program SQL Server kompiluje plan, gdy zmienna tabeli jest pusta i nie rekompiluje go po dodaniu wierszy. Spróbuj

DELETE FROM @IdList1 
WHERE Id IN (SELECT Id FROM @IdList2) 
OPTION (RECOMPILE) 

To uwzględnienia rzeczywistej liczby wierszy zawartych w zmiennej tabeli i pozbyć zagnieżdżonych pętli zaplanować

Oczywiście tworzenia indeksu Id poprzez ograniczenie może okazać się korzystne dla inne zapytania używające również zmiennej tabeli.

+0

To dla mnie nowość. Czy możesz wyjaśnić - kompilacja początkowa cacheplan miałaby miejsce, gdy napotkano instrukcję Delete, prawda? Nie kiedy deklarowane są zmienne tabeli? Chodzi mi o to, że plan jest kompilowany dla Delete, a nie dla deklaracji zmiennej tabeli ... Jeśli tak, to czy w tym miejscu nie zapełniłyby się zmienne tabeli? Ponadto, jeśli nie masz nic przeciwko, możesz podać referencję? Chciałbym to przeczytać. –

+2

@CharlesBretana - Istnieje kilka linków i przykładowy kod w [moja odpowiedź tutaj] (http://dba.stackexchange.com/questions/16385/whats-the-difference-between-a-temp-table-and-table- variable-in-sql-server) –

+0

dzięki ... Nauczyłeś się czegoś dzisiaj! –

2

Tabele w zmiennych stołowych może mieć klucze podstawowe, więc jeśli dane te potwierdzają wyjątkowość tych Id s, może być w stanie zwiększyć wydajność, przechodząc do

DECLARE @IdList1 TABLE(Id INT PRIMARY KEY) 
DECLARE @IdList2 TABLE(Id INT PRIMARY KEY) 
1

Używasz Table Variables, albo dodać klucz podstawowy do tabeli lub zmień je na Temporary Tables i dodaj INDEX. To spowoduje znacznie większą wydajność. Zgodnie z regułą, jeśli tabela jest tylko mała, użyj TABLE Variables, jednak jeśli tabela się rozwija i zawiera dużo danych, użyj tabeli tymczasowej.

-1

Spróbuj alternatywną składnię:

DELETE deleteAlias 
FROM @IdList1 deleteAlias 
WHERE EXISTS (
     SELECT NULL 
     FROM @IdList2 innerList2Alias 
     WHERE innerList2Alias.id=deleteAlias.id 
    ) 

EDIT .....................

Spróbuj użyć tabel #temp z indeksami.

Oto ogólny przykład, gdzie "DepartmentKey" to PK i FK.

IF OBJECT_ID('tempdb..#Department') IS NOT NULL 
begin 
     drop table #Department 
end 


CREATE TABLE #Department 
( 
    DepartmentKey int , 
    DepartmentName varchar(12) 
) 



CREATE INDEX IX_TEMPTABLE_Department_DepartmentKey ON #Department (DepartmentKey) 




IF OBJECT_ID('tempdb..#Employee') IS NOT NULL 
begin 
     drop table #Employee 
end 


CREATE TABLE #Employee 
( 
    EmployeeKey int , 
    DepartmentKey int , 
    SSN varchar(11) 
) 



CREATE INDEX IX_TEMPTABLE_Employee_DepartmentKey ON #Employee (DepartmentKey) 


Delete deleteAlias 
from #Department deleteAlias 
where exists (select null from #Employee innerE where innerE.DepartmentKey = deleteAlias.DepartmentKey) 





IF OBJECT_ID('tempdb..#Employee') IS NOT NULL 
begin 
     drop table #Employee 
end 

IF OBJECT_ID('tempdb..#Department') IS NOT NULL 
begin 
     drop table #Department 
end 
+0

Niestety jest to zbyt wolne. Ten sam wynik i ten sam plan zapytania. – hwcverwe

+0

Czy jesteś zmuszony używać @ tabel zmiennych, czy możesz wypróbować tabele #temp? – granadaCoder

+0

Jeśli możesz użyć tabel #temp, spróbuj przykładu z mojej odpowiedzi. – granadaCoder

2

Możliwe rozwiązania:

1) spróbuj utworzyć indeksy zatem

1.1) Jeżeli Lista {1 | 2} kolumna .ID posiada unikalne wartości, a następnie można określić unikatowy indeks klastra przy użyciu innego PK ograniczenie tak:

DECLARE @IdList1 TABLE(Id INT PRIMARY KEY); 
DECLARE @IdList2 TABLE(Id INT PRIMARY KEY); 

1,2) Jeżeli Lista {1 | 2} kolumna .ID mogą mieć zduplikowane wartości to można określić unikatowy indeks klastra przy użyciu ograniczenie PK przy użyciu manekina IDENTITY kolumna tak:

DECLARE @IdList1 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID)); 
DECLARE @IdList2 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID)); 

2) Spróbuj dodać HASH JOIN podpowiedź kwerendy tak:

DELETE list1 
FROM @IdList1 list1 
INNER JOIN @IdList2 list2 ON list1.Id = list2.Id 
OPTION (HASH JOIN); 
0

byłbym skłonny spróbować

DECLARE @IdList3 TABLE(Id INT); 

INSERT @IdList3 
SELECT Id FROM @IDList1 ORDER BY Id 
EXCEPT 
SELECT Id FROM @IDList2 ORDER BY Id 

Nie wymaga usuwania.

+0

Ale co, jeśli OP * potrzebuje * do usunięcia, tak jak on/ona powiedział: 'Muszę usunąć wszystkie rekordy z pierwszej listy, jeśli identyfikator już istnieje na drugiej liście' – oleksii

+0

@oleksii prawda, OP wskazuje na jego wymyślny przykład dotyczy tych dwóch zmiennych tabeli, a konkretnie delecje. Może to jednak być przydatne dla innego czytelnika. – Jodrell