Sono necessarie transazioni esplicite in questo ciclo while?


11

SQL Server 2014:

Abbiamo una tabella molto grande (100 milioni di righe) e dobbiamo aggiornare un paio di campi su di essa.

Per la spedizione di tronchi, ecc., Ovviamente, vogliamo anche mantenerlo in transazioni di dimensioni ridotte.

Se lasciamo che l'esecuzione di seguito venga eseguita per un po 'e quindi annulliamo / terminiamo la query, il lavoro svolto finora verrà eseguito il commit o dobbiamo aggiungere esplicite istruzioni BEGIN TRANSACTION / END TRANSACTION in modo da poter annullare in qualsiasi momento?

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END

Risposte:


13

Le singole dichiarazioni - DML, DDL, ecc. - sono transazioni in sé. Quindi sì, dopo ogni iterazione del loop (tecnicamente: dopo ogni istruzione), qualunque cosa sia UPDATEcambiata quell'istruzione è stata autoimpegnata.

Certo, c'è sempre un'eccezione, giusto? È possibile abilitare Transazioni implicite tramite SET IMPLICIT_TRANSACTIONS , nel qual caso la prima UPDATEistruzione darebbe inizio a una transazione che dovresti COMMITo ROLLBACKalla fine. Questa è un'impostazione a livello di sessione disattivata per impostazione predefinita nella maggior parte dei casi.

dobbiamo aggiungere esplicite istruzioni BEGIN TRANSACTION / END TRANSACTION in modo da poter annullare in qualsiasi momento?

No. E in effetti, dato che vuoi essere in grado di interrompere il processo e riavviare, aggiungere una transazione esplicita (o abilitare Transazioni implicite) sarebbe una cattiva idea dal momento che interrompere il processo potrebbe catturarlo prima che lo faccia COMMIT. In tal caso, è necessario emettere manualmente COMMIT(se ci si trova in SSMS) o se lo si sta eseguendo da un processo di Agente SQL, quindi non si ha tale opportunità e si potrebbe finire con una transazione orfana.


Inoltre, potresti voler impostare @CHUNK_SIZEun numero più piccolo. L'escalation dei blocchi avviene generalmente a 5000 blocchi acquisiti su un singolo oggetto. A seconda della dimensione delle righe e se sta eseguendo i blocchi di riga contro i blocchi di pagina, potresti superare questo limite. Se la dimensione di una riga è tale che si adattano solo 1 o 2 righe per ogni pagina, è possibile che si verifichi sempre ciò anche se si stanno eseguendo blocchi di pagina.

Se la tabella è partizionata, hai la possibilità di impostare l' LOCK_ESCALATIONopzione (introdotta in SQL Server 2008) per la tabella in AUTOmodo che blocchi solo la partizione e non l'intera tabella dopo l'escalation. Oppure, per qualsiasi tabella è possibile impostare la stessa opzione DISABLE, anche se si dovrebbe fare molta attenzione a questo. Vedi ALTER TABLE per i dettagli.

Ecco un po 'di documentazione che parla di Lock Escalation e delle soglie: Lock Escalation (si dice che si applica a "SQL Server 2008 R2 e versioni successive"). Ed ecco un post sul blog che si occupa di rilevare e correggere l'escalation dei blocchi: Blocco in Microsoft SQL Server (Parte 12 - Escalation dei blocchi) .


Non correlato alla domanda esatta, ma correlato alla query nella domanda, ci sono alcuni miglioramenti che potrebbero essere apportati qui (o almeno sembra così solo osservandola):

  1. Per il tuo ciclo, fare WHILE (@@ROWCOUNT = @CHUNK_SIZE)è leggermente meglio poiché se il numero di righe aggiornato sull'ultima iterazione è inferiore all'importo richiesto per AGGIORNARE, non c'è più lavoro da fare.

  2. Se il deletedcampo è un BITtipo di dati, quindi non è che il valore determinato da se o non deletedDateè 2000-01-01? Perché hai bisogno di entrambi?

  3. Se questi due campi sono nuovi e li hai aggiunti NULLperché potrebbe essere un'operazione online / non bloccante e ora desideri aggiornarli al loro valore "predefinito", non sarebbe necessario. A partire da SQL Server 2012 (solo Enterprise Edition), l'aggiunta di NOT NULLcolonne con vincolo DEFAULT sono operazioni non bloccanti purché il valore di DEFAULT sia costante. Pertanto, se non si utilizzano ancora i campi, rilasciare e aggiungere nuovamente come NOT NULLe con un vincolo DEFAULT.

  4. Se nessun altro processo sta aggiornando questi campi mentre si sta eseguendo questo AGGIORNAMENTO, sarebbe più veloce se si mettessero in coda i record che si desidera aggiornare e quindi si eliminasse semplicemente quella coda. Vi è un impatto sulle prestazioni nel metodo corrente poiché è necessario eseguire nuovamente la query sulla tabella ogni volta per ottenere il set che deve essere modificato. Invece, potresti fare quanto segue che scansiona la tabella solo una volta su quei due campi e quindi emette solo istruzioni UPDATE molto mirate. Non vi è inoltre alcuna penalità nell'arrestare il processo in qualsiasi momento e avviarlo in un secondo momento poiché la popolazione iniziale della coda troverà semplicemente i record rimasti per l'aggiornamento.

    1. Creare una tabella temporanea (#FullSet) che contiene solo i campi chiave dall'indice cluster.
    2. Creare una seconda tabella temporanea (#CurrentSet) della stessa struttura.
    3. inserire in #FullSet via SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;

      L' TOP(n)è lì a causa delle dimensioni della tabella. Con 100 milioni di righe nella tabella, non è davvero necessario popolare la tabella delle code con l'intero set di chiavi, soprattutto se si prevede di interrompere il processo ogni tanto e riavviarlo in seguito. Quindi, forse impostare nsu 1 milione e attendere fino al completamento. È sempre possibile pianificare ciò in un processo di SQL Agent che esegue il set di 1 milione (o forse anche meno) e quindi attende il prossimo orario programmato per riprendere. È quindi possibile pianificare l'esecuzione ogni 20 minuti in modo che ci sia un po 'di spazio di ventilazione forzato tra i set di n, ma finirà comunque l'intero processo incustodito. Quindi fai semplicemente cancellare il lavoro quando non c'è altro da fare :-).

    4. in un ciclo, fare:
      1. Popolare il batch corrente tramite qualcosa di simile DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
      2. IF (@@ROWCOUNT = 0) BREAK;
      3. Esegui l'AGGIORNAMENTO usando qualcosa come: UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
      4. Cancella il set corrente: TRUNCATE TABLE #CurrentSet;
  5. In alcuni casi è utile aggiungere un indice filtrato per assistere il SELECTche alimenta la #FullSettabella temporanea. Ecco alcune considerazioni relative all'aggiunta di un tale indice:
    1. La condizione WHERE dovrebbe corrispondere alla condizione WHERE della query, quindi WHERE deleted is null or deletedDate is null
    2. All'inizio del processo, la maggior parte delle righe corrisponderà alla condizione WHERE, quindi un indice non è molto utile. Potresti voler aspettare fino a qualche punto intorno al segno del 50% prima di aggiungere questo. Naturalmente, quanto aiuta e quando è meglio aggiungere l'indice varia a causa di diversi fattori, quindi è un po 'di tentativi ed errori.
    3. Potrebbe essere necessario AGGIORNARE STATISTICHE manualmente e / o REVISIONARE l'indice per mantenerlo aggiornato poiché i dati di base cambiano abbastanza frequentemente
    4. Assicurati di tenere presente che l'indice, pur aiutando SELECT, danneggerà UPDATEpoiché è un altro oggetto che deve essere aggiornato durante tale operazione, quindi più I / O. Questo si basa sia sull'utilizzo di un indice filtrato (che si restringe man mano che aggiorni le righe poiché meno righe corrispondono al filtro), sia sull'attesa un po 'di tempo per aggiungere l'indice (se all'inizio non sarà molto utile, quindi nessun motivo per incorrere l'I / O aggiuntivo).

AGGIORNAMENTO: Si prega di consultare la mia risposta a una domanda correlata a questa domanda per la piena attuazione di quanto suggerito sopra, incluso un meccanismo per tracciare lo stato e annullare in modo pulito: sql server: aggiornamento dei campi su una grande tabella in piccoli blocchi: come ottenere stato del progresso?


I tuoi suggerimenti in # 4 potrebbero essere più veloci in alcuni casi, ma sembra aggiungere una significativa complessità del codice. Preferirei iniziare in modo semplice, quindi se ciò non soddisfa le tue esigenze prendi in considerazione delle alternative.
Bacon Bits

@BaconBits Concordato sull'avvio semplice. Per essere onesti, questi suggerimenti non erano destinati a essere applicati a tutti gli scenari. La domanda riguarda la gestione di una tabella molto grande (100 milioni + righe).
Solomon Rutzky,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.