14

Ho problemi con le prestazioni SQL. Per una ragione improvvisa le seguenti domande sono molto lente:Molto lenta query DELETE

Ho due liste che contengono Id di una certa tabella. Ho bisogno di eliminare tutti i record dalla prima lista, se del Id esiste già nella seconda lista:

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) 

E 'possibile le due liste contiene più di 10.000 record. In tal caso, entrambe le query richiedono più di 20 secondi per essere eseguite.

Il piano di esecuzione mostrava anche qualcosa che non capisco. Forse questo spiega perché è così lento: Queryplan of both queries

I Ho riempito entrambe le liste con 10.000 numeri interi sequenziali così entrambe le liste contenevano il valore 1-10.000 come punto di partenza.

Come è possibile vedere entrambi i programmi di query per @ IdList2 Numero di righe effettivo è 50.005.000 !!. @ IdList1 è corretto (Numero effettivo di righe è 10.000)

So che ci sono altre soluzioni su come risolvere questo problema. Come riempire una terza lista instabile di rimozione dal primo elenco. Ma la mia domanda è:

Perché queste query di eliminazione sono così lente e perché vedo questi strani piani di query?

+0

Si tratta di un problema che potrebbe verificarsi in uno scenario reale o, solo in questa situazione specilaizzata? – Jodrell

+1

@Jodrell - I problemi sottostanti a nessuna ricompilazione basata sulle statistiche per le variabili di tabella (e la mancanza di indici utili su di essi) è molto comune. –

risposta

14

aggiungere una chiave primaria per le variabili di tabella e guardare urlare

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

perché non c'è alcun indice su queste variabili di tabella, qualsiasi join o sottoquery deve esaminare nell'ordine di 10.000 volte 10.000 = 100.000.000 paia di valori.

+0

Aiuterà avere un indice su '@ IdList1'? – Jodrell

+2

"Qualsiasi join o sottoquery deve esaminare nell'ordine di 10.000 volte 10.000 = 100.000.000 coppie di valori." questo è vero solo per i cicli annidati. Un hash o un join di unione elaborerebbe ogni input una sola volta (anche se un join di unione avrebbe bisogno anche di un ordinamento) –

+1

@martin, non ho letto quella roba per un po ', quindi ho dimenticato le regole, ma non sta scegliendo l'annidato loop perché non c'è un indice? Per fare gli altri algoritmi di loop non ha bisogno di un indice per ordinare i valori? Inoltre, senza un indice, deve ancora esaminare ogni coppia di valori, indipendentemente dall'algoritmo di loop utilizzato per crearli. - l'eccezione è che, come si nota, un join di unione, ma in questo caso deve presenziarli. –

12

SQL Server compila il piano quando la variabile di tabella è vuota e non ricompila quando vengono aggiunte righe. Prova

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

Ciò tiene conto del numero effettivo di righe contenute nella variabile tavolo e sbarazzarsi dei cicli annidati pianificare

Naturalmente la creazione di un indice su Id tramite un vincolo potrebbe essere utile per altre query che utilizzano anche la variabile table.

+0

Questo è nuovo per me. Puoi chiarire: la compilazione iniziale del cacheplan si verifica quando viene rilevata l'istruzione Delete, corretta? Non quando le variabili della tabella sono dichiarate? Voglio dire, il piano che si sta compilando è per la cancellazione, non per la dichiarazione della variabile di tabella ... Se sì, allora a quel punto non sarebbero popolate le variabili di tabella? Inoltre, se non ti dispiace, potresti fornire un riferimento? Mi piacerebbe leggere su questo. –

+2

@CharlesBretana - Ci sono alcuni link e codice di esempio in [la mia risposta qui] (http://dba.stackexchange.com/questions/16385/whats-the-difference- between-a-puntable-and-table- variable-in-sql-server) –

+0

grazie ... Ho imparato qualcosa oggi! –

2

Le tabelle di variabili di tabella possono avere le chiavi primarie, quindi se il vostro dati supportano l'unicità di questi Id s, si può essere in grado di migliorare le prestazioni andando per

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

Si utilizza Table Variables, o aggiungere un chiave primaria del tavolo o cambiarli in Temporary Tables e aggiungere uno INDEX. Ciò comporterà molte più prestazioni. Come regola generale, se la tabella è solo piccola, utilizzare TABLE Variables, tuttavia se la tabella è in espansione e contiene molti dati, utilizzare una tabella temporanea.

-1

Prova questa sintassi alternativa:

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

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

Provare a utilizzare le tabelle #temp con gli indici.

Ecco un esempio generico in cui "DepartmentKey" è il PK e l'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

Purtroppo anche questo è lento. Stesso risultato e stesso esatto piano di query. – hwcverwe

+0

Sei costretto a usare @ tabelle-variabili, o puoi provare le tabelle #temp? – granadaCoder

+0

Se è possibile utilizzare le tabelle #temp, provare l'esempio nella mia risposta. – granadaCoder

2

Possibili soluzioni:

1) tenta di creare indici così

1.1) Se List {1 | 2} colonna .ID ha valori univoci allora si potrebbe definire un indice cluster univoco utilizzando un vincolo di PK in questo modo:

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

1,2) Se List {1 | 2} colonna .ID può avere valori duplicati allora si potrebbe definire un indice cluster univoco utilizzando un vincolo PK utilizzando un manichino IDENTITY colonna in questo modo:

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) tenta di aggiungere HASH JOIN hint di query come questa:

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

sarei tentato di provare

DECLARE @IdList3 TABLE(Id INT); 

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

No cancellazione necessaria.

+0

Ma cosa succede se OP * deve * cancellare, come ha detto: 'Ho bisogno di cancellare tutti i record dal primo elenco se l'Id esiste già nel secondo elenco' – oleksii

+0

@oleksii true, l'OP indica un esempio forzato interessato a queste due variabili di tabella e in particolare alla cancellazione. Tuttavia, questo potrebbe essere ancora utile per un altro lettore. – Jodrell