Query di eliminazione duplicata SQL su milioni di righe per prestazioni
-
03-07-2019 - |
Domanda
Questa è stata un'avventura. Ho iniziato con la query duplicata in loop situata in la mia domanda precedente , ma ogni ciclo avrebbe superato tutti i 17 milioni di record , significa che occorrerebbero settimane (solo l'esecuzione di * select count * da MyTable *
richiede il mio server 4:30 minuti usando MSSQL 2005). Ho brillato di informazioni da questo sito e in questo post .
E sono arrivato alla query qui sotto. La domanda è: è questo il tipo corretto di query da eseguire su 17 milioni di record per qualsiasi tipo di prestazione? In caso contrario, cos'è?
SQL QUERY:
DELETE tl_acxiomimport.dbo.tblacxiomlistings
WHERE RecordID in
(SELECT RecordID
FROM tl_acxiomimport.dbo.tblacxiomlistings
EXCEPT
SELECT RecordID
FROM (
SELECT RecordID, Rank() over (Partition BY BusinessName, latitude, longitude, Phone ORDER BY webaddress DESC, caption1 DESC, caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
) al WHERE Rank = 1)
Soluzione
Vedere il QueryPlan sarebbe di aiuto.
È possibile?
SELECT m.*
into #temp
FROM tl_acxiomimport.dbo.tblacxiomlistings m
inner join (SELECT RecordID,
Rank() over (Partition BY BusinessName,
latitude,
longitude,
Phone
ORDER BY webaddress DESC,
caption1 DESC,
caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
) al on (al.RecordID = m.RecordID and al.Rank = 1)
truncate table tl_acxiomimport.dbo.tblacxiomlistings
insert into tl_acxiomimport.dbo.tblacxiomlistings
select * from #temp
Altri suggerimenti
Qualcosa non va con il tuo DB, server, memoria o una combinazione di questi. 4:30 per un conteggio selezionato * sembra MOLTO alto.
Esegui un DBCC_SHOWCONTIG per vedere quanto è frammentata la tua tabella, questo potrebbe causare un notevole calo delle prestazioni su una tabella di quelle dimensioni.
Inoltre, per aggiungere al commento di RyanKeeter, esegui il piano dello spettacolo e se ci sono scansioni di tabelle crea un indice per il campo PK su quella tabella.
Non sarebbe più semplice da fare:
DELETE tl_acxiomimport.dbo.tblacxiomlistings
WHERE RecordID in
(SELECT RecordID
FROM (
SELECT RecordID,
Rank() over (Partition BY BusinessName,
latitude,
longitude,
Phone
ORDER BY webaddress DESC,
caption1 DESC,
caption2 DESC) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
)
WHERE Rank > 1
)
Esegui questo nell'analizzatore di query:
SET SHOWPLAN_TEXT ON
Quindi chiedi all'analizzatore di query di eseguire la tua query. Invece di eseguire la query, SQL Server genererà un piano di query e lo inserirà nel set di risultati.
Mostraci il piano di query.
17 milioni di record non sono niente. Se sono necessarie le 4:30 per eseguire solo un conteggio selezionato (*), si verifica un problema grave, probabilmente correlato alla mancanza di memoria nel server o a un processore davvero vecchio.
Per prestazioni, riparare la macchina. Pompalo fino a 2 GB. La RAM è così economica in questi giorni che il suo costo è molto inferiore al tuo tempo.
Il processore o il disco si sta bloccando quando la query è in corso? In caso contrario, qualcosa sta bloccando le chiamate. In tal caso, potresti considerare di mettere il database in modalità utente singolo per il tempo necessario per eseguire la pulizia.
Quindi stai eliminando tutti i record che non sono classificati per primi? Potrebbe valere la pena confrontare un join con una delle prime 1 sub query (che potrebbe funzionare anche nel 2000, poiché il grado è solo il 2005 e oltre)
Devi rimuovere tutti i duplicati in un'unica operazione? Presumo che tu stia eseguendo una sorta di compito di pulizia, potresti essere in grado di farlo saggiamente.
Fondamentalmente creare un cursore che avvolge tutti i record (lettura sporca) e rimuove i duplicati per ciascuno. Nel complesso sarà molto più lento, ma ogni operazione sarà relativamente minima. Quindi le pulizie diventano un'attività in background costante anziché un batch notturno.
Il suggerimento sopra per selezionare prima un tavolo temporaneo è la soluzione migliore. Puoi anche usare qualcosa come:
set rowcount 1000
prima di eseguire la tua cancellazione. Smetterà di funzionare dopo aver eliminato le 1000 righe. Quindi eseguilo ancora e ancora fino a quando non ottieni 0 record cancellati.
se lo capisco correttamente, la tua richiesta è uguale a
DELETE tl_acxiomimport.dbo.tblacxiomlistings
FROM
tl_acxiomimport.dbo.tblacxiomlistings allRecords
LEFT JOIN (
SELECT RecordID, Rank() over (Partition BY BusinessName, latitude, longitude, Phone ORDER BY webaddress DESC, caption1 DESC, caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
WHERE Rank = 1) myExceptions
ON allRecords.RecordID = myExceptions.RecordID
WHERE
myExceptions.RecordID IS NULL
Penso che dovrebbe funzionare più velocemente, tendo ad evitare di utilizzare " IN " clausola a favore dei JOIN ove possibile.
Puoi effettivamente testare la velocità e i risultati in modo sicuro semplicemente chiamando SELECT *
o SELECT COUNT (*)
sulla parte FROM come ad esempio
SELECT *
FROM
tl_acxiomimport.dbo.tblacxiomlistings allRecords
LEFT JOIN (
SELECT RecordID, Rank() over (Partition BY BusinessName, latitude, longitude, Phone ORDER BY webaddress DESC, caption1 DESC, caption2 DESC ) AS Rank
FROM tl_acxiomimport.dbo.tblacxiomlistings
WHERE Rank = 1) myExceptions
ON allRecords.RecordID = myExceptions.RecordID
WHERE
myExceptions.RecordID IS NULL
Questo è un altro motivo per cui preferirei l'approccio JOIN Spero che ciò aiuti
Questo sembra a posto ma potresti prendere in considerazione la possibilità di selezionare i tuoi dati in una tabella temporanea e di usarli nella tua dichiarazione di eliminazione. Ho notato enormi miglioramenti delle prestazioni facendo questo invece di fare tutto in quell'unica query.
Ricorda che quando esegui una cancellazione di grandi dimensioni è meglio avere prima un buon backup (e di solito copio anche i record eliminati su un'altra tabella per ogni evenienza, devo recuperarli subito).
Oltre a utilizzare troncato come suggerito, ho avuto la fortuna di utilizzare questo modello per eliminare molte righe da una tabella. Non ricordo di esserlo, ma penso che l'utilizzo della transazione abbia contribuito a impedire la crescita del file di registro - potrebbe essere stato un altro motivo - non sono sicuro. E di solito cambio il metodo di registrazione delle transazioni su semplice prima di fare qualcosa del genere:
SET ROWCOUNT 5000 WHILE 1 = 1 BEGIN begin tran DELETE FROM ??? WHERE ??? IF @@rowcount = 0 BEGIN COMMIT BREAK END COMMIT END SET ROWCOUNT 0