Domanda

Una query utilizzata per scorrere 17 milioni di record per rimuovere i duplicati è in esecuzione da circa 16 ore e volevo sapere se la query è stata interrotta correttamente ora se finalizzerà le istruzioni di eliminazione o se è stato eliminato durante l'esecuzione di questa query? In effetti, se lo interrompo, finalizza le eliminazioni o ripristina?

L'ho scoperto quando faccio un

 select count(*) from myTable

Che le righe che restituisce (mentre esegue questa query) è circa 5 in meno rispetto al conteggio delle righe iniziali. Ovviamente le risorse del server sono estremamente scarse, quindi questo significa che questo processo ha impiegato 16 ore per trovare 5 duplicati (quando in realtà ce ne sono migliaia), e questo potrebbe essere attivo per giorni?

Questa query ha richiesto 6 secondi su 2000 righe di dati di test e funziona perfettamente su quel set di dati, quindi ho pensato che ci sarebbero volute 15 ore per il set completo.

Qualche idea?

Di seguito è la query:

--Declare the looping variable
DECLARE @LoopVar char(10)


    DECLARE
     --Set private variables that will be used throughout
      @long DECIMAL,
      @lat DECIMAL,
      @phoneNumber char(10),
      @businessname varchar(64),
      @winner char(10)

    SET @LoopVar = (SELECT MIN(RecordID) FROM MyTable)

    WHILE @LoopVar is not null
    BEGIN

      --initialize the private variables (essentially this is a .ctor)
      SELECT 
        @long = null,
        @lat = null,
        @businessname = null,
        @phoneNumber = null,
        @winner = null

      -- load data from the row declared when setting @LoopVar  
      SELECT
        @long = longitude,
        @lat = latitude,
        @businessname = BusinessName,
        @phoneNumber = Phone
      FROM MyTable
      WHERE RecordID = @LoopVar

      --find the winning row with that data. The winning row means 
      SELECT top 1 @Winner = RecordID
      FROM MyTable
      WHERE @long = longitude
        AND @lat = latitude
        AND @businessname = BusinessName
        AND @phoneNumber = Phone
      ORDER BY
        CASE WHEN webAddress is not null THEN 1 ELSE 2 END,
        CASE WHEN caption1 is not null THEN 1 ELSE 2 END,
        CASE WHEN caption2 is not null THEN 1 ELSE 2 END,
        RecordID

      --delete any losers.
      DELETE FROM MyTable
      WHERE @long = longitude
        AND @lat = latitude
        AND @businessname = BusinessName
        AND @phoneNumber = Phone
        AND @winner != RecordID

      -- prep the next loop value to go ahead and perform the next duplicate query.
      SET @LoopVar = (SELECT MIN(RecordID) 
    FROM MyTable
    WHERE @LoopVar < RecordID)
    END
È stato utile?

Soluzione

no, il server sql non eseguirà il rollback delle eliminazioni già eseguite se si interrompe l'esecuzione della query. oracle richiede un esplicito commit di query di azione oppure i dati vengono ripristinati, ma non mssql.

con il server sql non eseguirà il rollback a meno che non sia specificamente in esecuzione nel contesto di una transazione e non si esegua il rollback di tale transazione o la connessione venga chiusa senza il commit della transazione. ma non vedo un contesto di transazione nella query sopra.

potresti anche provare a ristrutturare la tua query per rendere le cancellazioni un po 'più efficienti, ma essenzialmente se le specifiche del tuo box non sono all'altezza, potresti rimanere bloccato ad aspettarlo.

andando avanti, dovresti creare un indice univoco sul tavolo per impedirti di ripetere questo.

Altri suggerimenti

La tua query non è inserita in una transazione, quindi non eseguirà il rollback delle modifiche già apportate dalle singole istruzioni di eliminazione.

L'ho provato specificatamente sul mio SQL Server usando la seguente query e la tabella ApplicationLog era vuota anche se ho annullato la query:

declare @count int
select @count = 5
WHILE @count > 0
BEGIN
  print @count
  delete from applicationlog;
  waitfor time '20:00';
  select @count = @count -1
END

Tuttavia è probabile che la tua query impieghi molti giorni o settimane, molto più a lungo di 15 ore. La tua stima che puoi elaborare 2000 record ogni 6 secondi è errata perché ogni iterazione nel tuo ciclo while impiegherà molto più tempo con 17 milioni di righe rispetto a 2000 righe. Quindi, a meno che la tua query non richieda significativamente meno di un secondo per 2000 righe, ci vorranno giorni per tutti i 17 milioni.

Dovresti porre una nuova domanda su come eliminare in modo efficiente le righe duplicate.

Se non si fa nulla di esplicito sulle transazioni, la connessione sarà in transazioni con autocommit . In questa modalità ogni istruzione SQL è considerata una transazione.

La domanda è se questo significhi che le singole istruzioni SQL sono transazioni e pertanto vengono impegnate mentre si procede, o se il ciclo WHILE esterno viene considerato come una transazione.

Non sembra esserci alcuna discussione al riguardo nella descrizione del costrutto WHILE su MSDN . Tuttavia, poiché un'istruzione WHILE non può modificare direttamente il database, sembrerebbe logico che non avvii una transazione con commit automatico.

Transazioni implicite

Se non è stata impostata alcuna "Transazione implicita", ogni iterazione nel ciclo ha eseguito le modifiche.

È possibile impostare qualsiasi SQL Server con "Transazioni implicite". Questa è un'impostazione del database (per impostazione predefinita è OFF). Puoi anche avere transazioni implicite nelle proprietà di una particolare query all'interno di Management Studio (fai clic con il pulsante destro del mouse sul riquadro delle query & opzioni;), per impostazione predefinita nel client o un'istruzione SET.

SET IMPLICIT_TRANSACTIONS ON;

In entrambi i casi, in tal caso, sarà comunque necessario eseguire un COMMIT / ROLLBACK esplicito indipendentemente dall'interruzione dell'esecuzione della query.


Riferimento alle transazioni implicite:

http://msdn.microsoft.com/en-us/library /ms188317.aspx

http://msdn.microsoft.com/en-us/library /ms190230.aspx

Ho ereditato un sistema che aveva una logica come la tua implementata in SQL. Nel nostro caso, stavamo provando a collegare tra loro le righe usando la corrispondenza fuzzy che aveva nomi / indirizzi simili, ecc. E quella logica era fatta puramente in SQL. Nel momento in cui l'ho ereditato, nella tabella c'erano circa 300.000 righe e, secondo i tempi, abbiamo calcolato che ci sarebbe voluto UN ANNO per abbinarli tutti.

Come esperimento per vedere quanto più velocemente potrei farlo al di fuori di SQL, ho scritto un programma per scaricare la tabella db in file flat, leggere i file flat in un programma C ++, creare i miei indici e fare il fuzzy logica lì, quindi reimportare i file flat nel database. Ciò che ha richiesto UN ANNO in SQL ha impiegato circa 30 secondi nell'app C ++.

Quindi, il mio consiglio è, non provare nemmeno quello che stai facendo in SQL. Esporta, elabora, reimporta.

LE CANCELLA che sono state eseguite fino a questo punto non verranno ripristinate.


Come autore originale del codice in questione e dopo aver avvertito che le prestazioni dipenderanno dagli indici, proporrei i seguenti elementi per accelerare ciò.

RecordId meglio essere PRIMARY KEY. Non intendo IDENTITÀ, intendo CHIAVE PRIMARIA. Confermare con sp_help

Alcuni indici dovrebbero essere usati per valutare questa query. Scopri quale di queste quattro colonne ha il minor numero di ripetizioni e indicizza quella ...

SELECT *
FROM MyTable
WHERE @long = longitude
  AND @lat = latitude
  AND @businessname = BusinessName
  AND @phoneNumber = Phone

Prima e dopo aver aggiunto questo indice, controlla il piano di query per vedere se è stata aggiunta la scansione dell'indice.

Come ciclo la tua query farà fatica a ridimensionarsi bene, anche con indici appropriati. La query deve essere riscritta in una singola istruzione, come da suggerimenti in la tua domanda precedente su questo.

Se non lo si esegue esplicitamente all'interno di una transazione, verrà ripristinata solo la dichiarazione di esecuzione.

Penso che questa query sarebbe molto più efficiente se fosse riscritta usando un algoritmo single-pass usando un cursore. Ordineresti la tabella dei cursori per longitudine, latitudine, BusinessName E @phoneNumber. Devi attraversare le file una alla volta. Se una riga ha la stessa longitudine, latitudine, nome commerciale e numero di telefono della riga precedente, eliminarla.

Penso che tu debba considerare seriamente la tua metodologia.  È necessario iniziare a pensare in set (anche se per le prestazioni potrebbe essere necessario l'elaborazione in batch, ma non riga per riga rispetto a una tabella record da 17 milioni.)

Per prima cosa tutti i tuoi record hanno duplicati? Sospetto di no, quindi la prima cosa che vuoi fare è limitare la tua elaborazione ai soli record che hanno duplicati. Poiché si tratta di una tabella di grandi dimensioni e potrebbe essere necessario eseguire le eliminazioni in batch nel tempo, a seconda dell'altra elaborazione in corso, è innanzitutto necessario estrarre i record che si desidera gestire in una tabella propria che quindi si indicizza. Puoi anche usare una tabella temporanea se sarai in grado di farlo tutto allo stesso tempo senza mai fermarlo altrimenti crea una tabella nel tuo database e rilasciala alla fine.

Qualcosa del tipo (Nota che non ho scritto le istruzioni di creazione dell'indice, immagino che tu possa cercarlo da solo):

SELECT min(m.RecordID), m.longitude, m.latitude, m.businessname, m.phone  
     into  #RecordsToKeep    
FROM MyTable   m
join 
(select longitude, latitude, businessname, phone
from MyTable
group by longitude, latitude, businessname, phone
having count(*) >1) a 
on a.longitude = m.longitude and a.latitude = m.latitude and
a.businessname = b.businessname and a.phone = b.phone 
group by  m.longitude, m.latitude, m.businessname, m.phone   
ORDER BY CASE WHEN m.webAddress is not null THEN 1 ELSE 2 END,        
    CASE WHEN m.caption1 is not null THEN 1 ELSE 2 END,        
    CASE WHEN m.caption2 is not null THEN 1 ELSE 2 END



while (select count(*) from #RecordsToKeep) > 0
begin
select top 1000 * 
into #Batch
from #RecordsToKeep

Delete m
from mytable m
join #Batch b 
        on b.longitude = m.longitude and b.latitude = m.latitude and
        b.businessname = b.businessname and b.phone = b.phone 
where r.recordid <> b.recordID

Delete r
from  #RecordsToKeep r
join #Batch b on r.recordid = b.recordid

end

Delete m
from mytable m
join #RecordsToKeep r 
        on r.longitude = m.longitude and r.latitude = m.latitude and
        r.businessname = b.businessname and r.phone = b.phone 
where r.recordid <> m.recordID

Prova anche a pensare a un altro metodo per rimuovere le righe duplicate:

delete t1 from table1 as t1 where exists (
    select * from table1 as t2 where
        t1.column1=t2.column1 and
        t1.column2=t2.column2 and
        t1.column3=t2.column3 and
        --add other colums if any
        t1.id>t2.id
)

Suppongo che tu abbia una colonna ID intero nella tua tabella.

Se la tua macchina non ha hardware molto avanzato, il completamento del comando potrebbe richiedere molto tempo al server sql. Non so per certo come questa operazione venga eseguita sotto il cofano, ma in base alla mia esperienza questo potrebbe essere fatto in modo più efficiente portando i record fuori dal database e in memoria per un programma che utilizza una struttura ad albero con una regola di rimozione duplicata per inserimento. Prova a leggere l'intera tabella in blocchi (diciamo 10000 righe alla volta) in un programma C ++ usando ODBC. Una volta nel programma C ++ usa e std :: map dove key è la chiave univoca e struct è una struttura che contiene il resto dei dati in variabili. Scorri su tutti i record ed esegui l'inserimento nella mappa. La funzione di inserimento della mappa gestirà la rimozione dei duplicati. Poiché la ricerca all'interno di una mappa è lg (n) tempo molto meno tempo per trovare duplicati rispetto all'utilizzo del ciclo while. È quindi possibile eliminare l'intera tabella e aggiungere nuovamente le tuple nel database dalla mappa formando query di inserimento ed eseguendole tramite odbc o creando uno script di file di testo ed eseguendolo in Management Studio.

Sono abbastanza sicuro che sia negativo. Altrimenti quale sarebbe il punto delle transazioni?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top