2013-02-09 13 views
6

Durante un progetto di migrazione, sono confrontato con un aggiornamento di 4 milioni di record nel nostro SQL Server.Aggiornamento di 4 milioni di record nel server SQL utilizzando l'elenco di ID record come input

L'aggiornamento è molto semplice; un campo booleano deve essere impostato su true/1 e l'input che ho è un elenco di tutti gli id ​​per i quali questo campo deve essere compilato. (un id per riga)

Non sono esattamente un esperto quando arriva a compiti sql di queste dimensioni, quindi ho iniziato a provare 1 istruzione UPDATE contenente un "WHERE xxx IN ({list of ids, separated by comma})". Innanzitutto, ho provato questo con un milione di record. Su un piccolo set di dati su un server di test, questo ha funzionato come un fascino, ma nell'ambiente di produzione questo ha dato un errore. Quindi, ho accorciato la lunghezza della lista di id un paio di volte, ma senza successo.

La prossima cosa che ho provato è stata di trasformare ogni id nella lista in un'istruzione UPDATE ("UPDATE yyy SET booleanfield = 1 WHERE id = '{id}'"). Da qualche parte, ho letto che è bello avere un GO ogni x numero di linee, così ho inserito un GO ogni 100 righe (usando l'eccellente strumento 'sed', portato da unix).

Quindi, ho separato l'elenco di 4 milioni di istruzioni di aggiornamento in parti di 250.000 ciascuna, le ho salvate come file sql e ho iniziato a caricare ed eseguire il primo in SQL Server Management Studio (2008). Si noti che ho anche provato SQLCMD.exe, ma con mia sorpresa, questo ha funzionato circa 10-20 volte più lentamente di SQL Studio.

Ci sono voluti circa 1,5 ore per completare e il risultato è "Query completata con errori". L'elenco dei messaggi conteneva tuttavia una bella lista di "1 riga (e) interessata" e "0 righe (o) interessate", quest'ultima per quando l'id non era stata trovata.

Successivamente, ho controllato la quantità di record aggiornati nella tabella utilizzando un COUNT (*) e ho riscontrato una differenza di un paio di migliaia di record tra la quantità di istruzioni di aggiornamento e la quantità di record aggiornati.

Ho quindi pensato che ciò potesse essere dovuto ai record inesistenti, ma quando ho sottratto la quantità di "0 righe interessate" nell'output, c'era un misterioso divario di 895 record.

Le mie domande:

  1. C'è un modo per scoprire una descrizione e la causa degli errori in "Query completate con errori".

  2. Come si può spiegare il misterioso gap di 895 record?

  3. Qual è un modo migliore o migliore per eseguire questo aggiornamento? (Come sto iniziando a pensare quello che sto facendo potrebbe essere molto inefficiente e/o soggetto a errori)

+0

Gap sembra essere associato sia al fatto che si avevano id duplicati (che è certamente una possibilità con 4 milioni di record) o che l'id non esisteva bin il tavolo (un'altra possibilità). –

+0

Come hai ottenuto quei record da 4 mil in una stringa csv? – Kaf

+0

quanti record ci sono nella tabella totale? Sto pensando che se ci sono poco più di 4 milioni, potrebbe essere meglio eliminare la colonna, aggiungerla con un valore predefinito di 1 e aggiornare le altre righe a 0 o null. – UnhandledExcepSean

risposta

6

Il modo migliore per avvicinarsi a questa domanda è inserendo i 4 milioni di record in una tabella. Di fatto, puoi inserirli in una tabella con una colonna Identity, inserendo "bulk" in una vista.

create table TheIds (rownum int identity(1,1), id int); 

create view v_TheIds (select id from TheIds); 

bulk insert into v_TheIds . . . 

Con tutti i dati nel database, ora avete molte più opzioni. Provare l'aggiornamento:

update t 
    set booleanfield = 1 
    where exists (select 1 from TheIds where TheIds.id = t.id) 

È inoltre necessario creare un indice su TheIds(id).

Questo è un aggiornamento di grandi dimensioni, tutto in esecuzione come un'unica transazione. Questo può avere implicazioni di prestazioni cattive e iniziare a riempire il registro. Si può rompere in transazioni più piccole utilizzando la colonna rownum:

update t 
    set booleanfield = 1 
    where exists (select 1 from TheIds where TheIds.id = t.id and TheIds.rownum < 1000) 

La clausola esiste qui sta facendo l'equivalente del left outer join. La principale differenza è che questa sintassi di subquery correlata dovrebbe funzionare in altri database, dove i join con gli aggiornamenti sono specifici del database.

Con la colonna rownum, è possibile selezionare tutte le righe desiderate per l'aggiornamento. Così, si può mettere l'aggiornamento in un ciclo, se l'aggiornamento generale è troppo grande:

where rownum < 100000 
where rownum between 100000 and 199999 
where rownum between 200000 and 299999 

e così via. Non è necessario farlo, ma è possibile se si desidera eseguire il batch degli aggiornamenti per qualche motivo.

L'idea chiave è ottenere l'elenco degli ID in una tabella nel database, in modo da poter utilizzare la potenza del database per le operazioni successive.

+0

Questo suona bene e sembra piuttosto semplice, che è il migliore nella maggior parte dei casi :) Grazie mille Gordon, lo proveremo e pubblicheremo i risultati qui. –

+0

Incredibile quanto velocemente 4 milioni di ID siano stati inseriti in una tabella da SQL Server ... Sono stupito. Fin qui tutto bene. Non sono sicuro di cosa intendi per "selezionare 1 da TheIds dove TheIds.id = t.id e TheIds.rownum <1000" in relazione a "Puoi suddividerlo in transazioni più piccole utilizzando la colonna rownum". Potresti spiegare per favore? –

+0

Ho ridotto i 4 milioni in 8 parti utilizzando il "trucco" del rownumber e ho finito per aggiornare tutti i 4 milioni di record in meno di un'ora. Sbalorditivo. Grazie Gordon! –

4

Attenzione: non sono stato in grado di provarlo e non ho un "database parco giochi "che può contenere tanti dati.

io non sono sicuro di 1. e 2., ma per 3. Si dovrebbe essere meglio lasciare la limitazione dell'aggiornamento al DB:

UPDATE TOP(100000) yyy 
SET booleanfield = 1 
WHERE booleanfield = 0 
GO 

se la documentazione dice a "selezionare casualmente" un po ' voci con quella limitazione TOP - Spero che lo faccia solo da quelli che hanno booleanfield = 0. Esegui questa query ripetutamente finché non vengono segnalati altri aggiornamenti.

Un'altra opzione se quanto sopra non funziona è quello di selezionare gli ID colpite direttamente dal DB ... questo sembra strano e non ho provato neanche, ma spero che funziona:

UPDATE yyy 
SET booleanfield = 1 
FROM (SELECT TOP 100000 id FROM yyy WHERE booleanfield = 0 ORDER BY id ASC) AS xxxx 
WHERE yyy.id = xxxx.id; 
GO 

(Presumo che tu abbia una chiave univoca id nella tabella qui). Esegui questa query diverse (circa 40 volte) finché non vengono segnalati altri aggiornamenti.

+0

Clemens, non sapevo del trucco TOP(). Buono a sapersi. Grazie –