Satement dell'aggiornamento SQL che richiede molto tempo / elevato utilizzo del disco per ore


8

Sì, sembra un problema molto generico, ma non sono stato ancora in grado di restringerlo molto.

Quindi ho una dichiarazione UPDATE in un file batch sql:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID

B ha 40k record, A ha 4M record e sono correlati da 1 a n tramite A.B_ID, sebbene non ci sia FK tra i due.

Quindi sostanzialmente sto pre-calcolando un campo per scopi di data mining. Anche se ho cambiato il nome delle tabelle per questa domanda, non ho cambiato la dichiarazione, è davvero così semplice.

Questa operazione richiede ore, quindi ho deciso di annullare tutto. Il DB è stato danneggiato, quindi l'ho eliminato, ripristinato un backup che ho fatto poco prima di eseguire l'istruzione e ho deciso di approfondire i dettagli con un cursore:

DECLARE CursorB CURSOR FOR SELECT ID FROM B ORDER BY ID DESC -- Descending order
OPEN CursorB 
DECLARE @Id INT
FETCH NEXT FROM CursorB INTO @Id

WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @Msg VARCHAR(50) = 'Updating A for B_ID=' + CONVERT(VARCHAR(10), @Id)
    RAISERROR(@Msg, 10, 1) WITH NOWAIT

    UPDATE A
    SET A.X = B.X
    FROM A JOIN B ON A.B_ID = B.ID
    WHERE B.ID = @Id

    FETCH NEXT FROM CursorB INTO @Id
END

Ora posso vederlo in esecuzione con un messaggio con l'id decrescente. Quello che succede è che ci vogliono circa 5 minuti per passare da id = 40k a id = 13

E poi a id 13, per qualche motivo, sembra bloccarsi. Il DB non ha alcuna connessione ad esso oltre a SSMS, ma in realtà non è impiccato:

  • il disco rigido funziona continuamente, quindi sta sicuramente facendo qualcosa (ho verificato in Process Explorer che è effettivamente il processo sqlserver.exe che lo utilizza)
  • Ho eseguito sp_who2, ho trovato lo SPID (70) della sessione SUSPENDED, quindi ho eseguito il seguente script:

    seleziona * da sys.dm_exec_requests r unisciti sys.dm_os_tasks t su r.session_id = t.session_id dove r.session_id = 70

Questo mi dà il wait_type, che è PAGEIOLATCH_SH il più delle volte ma in realtà cambia a WRITE_COMPLETION a volte, il che immagino accade quando sta svuotando il registro

  • il file di registro, che era 1,6 GB quando ho ripristinato il DB (e quando è arrivato a id 13), ora è 3,5 GB

Altre informazioni forse utili:

  • il numero di record nella tabella A per B_ID 13 non è elevato (14)
  • La mia collega non ha lo stesso problema sulla sua macchina, con una copia di questo DB (di un paio di mesi fa) con la stessa struttura.
  • la tabella A è di gran lunga la più grande tabella nel DB
  • Ha diversi indici e diverse viste indicizzate lo utilizzano.
  • Non ci sono altri utenti nel DB, è locale e nessuna applicazione lo sta usando.
  • Il file LDF non ha dimensioni limitate.
  • Il modello di recupero è SEMPLICE, il livello di compatibilità è 100
  • Procmon non mi fornisce molte informazioni: sqlserver.exe legge e scrive molto dai file MDF e LDF.

Sto ancora aspettando che finisca (sono passate le 1h30) ma speravo che forse qualcuno mi avrebbe dato qualche altra azione per provare a risolvere questo problema.

Modificato: aggiunta di estratto dal registro di procmon

15:24:02.0506105    sqlservr.exe    1760    ReadFile    C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 5,498,732,544, Length: 8,192, I/O Flags: Non-cached, Priority: Normal
15:24:02.0874427    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 6,225,805,312, Length: 16,384, I/O Flags: Non-cached, Write Through, Priority: Normal
15:24:02.0884897    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA_1.LDF    SUCCESS Offset: 4,589,289,472, Length: 8,388,608, I/O Flags: Non-cached, Write Through, Priority: Normal

Dall'uso di DBCC PAGE sembra che si stia leggendo e scrivendo in campi che assomigliano alla tabella A (o a uno dei suoi indici), ma per B_ID diversi che 13. Forse la ricostruzione degli indici?

Modificato 2: piano di esecuzione

Quindi ho annullato la query (effettivamente cancellato il DB e i suoi file e poi ripristinato), e ho controllato il piano di esecuzione per:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = 13

Il piano di esecuzione (stimato) è lo stesso di qualsiasi B.ID e sembra abbastanza semplice. La clausola WHERE utilizza una ricerca indice su un indice non cluster di B, JOIN utilizza una ricerca indice cluster su entrambi i PK delle tabelle. La ricerca dell'indice cluster su A utilizza il parallelismo (x7) e rappresenta il 90% del tempo della CPU.

Ancora più importante, l'esecuzione effettiva della query con ID 13 è immediata.

Modificato 3: frammentazione dell'indice

La struttura degli indici è la seguente:

B ha un PK cluster (non il campo ID) e un indice univoco non cluster, il cui primo campo è B.ID - questo secondo indice sembra essere usato sempre.

A ha un PK cluster (campo non correlato).

Ci sono anche 7 viste su A (tutte includono il campo AX), ognuna con il proprio PK cluster e un altro indice che include anche il campo AX

Le viste sono filtrate (con campi che non sono in questa equazione), quindi dubito che ci sia un modo in cui AGGIORNAMENTO A userebbe le viste stesse. Ma hanno un indice che include AX, quindi cambiare AX significa scrivere le 7 viste e i 7 indici che hanno quel campo.

Sebbene l'AGGIORNAMENTO dovrebbe essere più lento per questo, non c'è motivo per cui un ID specifico sia molto più lungo degli altri.

Ho verificato la frammentazione di tutti gli indici, tutti erano <0,1%, ad eccezione degli indici secondari delle viste , tutti compresi tra il 25% e il 50%. I fattori di riempimento per tutti gli indici sembrano ok, tra il 90% e il 95%.

Ho riorganizzato tutti gli indici secondari e riproposto la mia sceneggiatura.

È ancora impiccato, ma in un punto diverso:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

Considerando che in precedenza, il registro messaggi appariva così:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

        Updating A for B_ID=13

Questo è strano, perché significa che non è nemmeno impiccato nello stesso punto del WHILEloop. Il resto ha lo stesso aspetto: stessa riga UPDATE in attesa in sp_who2, stesso tipo di attesa PAGEIOLATCH_EX e stesso utilizzo intenso di HD da sqlserver.exe.

Il prossimo passo è eliminare tutti gli indici e le viste e ricrearli, credo.

Modificato 4: eliminazione quindi ricostruzione degli indici

Quindi, ho eliminato tutte le viste indicizzate che avevo sulla tabella (7 di esse, 2 indici per vista, inclusa quella raggruppata). Ho eseguito lo script iniziale (senza cursore) e in realtà è stato eseguito in 5 minuti.

Quindi il mio problema deriva dall'esistenza di questi indici.

Ho ricreato i miei indici dopo aver eseguito l'aggiornamento e ci sono voluti 16 minuti.

Ora capisco che gli indici richiedono tempo per essere ricostruiti, e in realtà sto bene con l'operazione completa che richiede 20 minuti.

Quello che ancora non capisco è, perché quando eseguo l'aggiornamento senza eliminare prima gli indici, ci vogliono diverse ore, ma quando li elimino per primi e poi li ricrea, ci vogliono 20 minuti. Non dovrebbe impiegare all'incirca la stessa ora in entrambi i modi?


1
Qualcosa nel registro degli errori di SQL Server? Anche da procmon quali sono gli offset nel file su cui sta scrivendo? Puoi dividere per 8.192 per ottenere la pagina e poi usare DBCC PAGEper vedere su cosa viene scritto.
Martin Smith,

3,5 GB sembra la massima quantità di RAM che un sistema operativo Windows a 32 bit è in grado di gestire.
tschmit007,

@MartinSmith Non c'è assolutamente nulla da quando ho ripristinato i registri di SQL Server SSMS e nulla nel registro eventi di Windows
GFK

Che aspetto hanno i tuoi indici sulla tabella A (quali colonne, ecc.)? Sono frammentati?
Stuart Ainsworth,

@ tschmit007 SQL Edition R2 x64 Dev Edition su Win Server 2008 R2 x64. È una macchina virtuale in esecuzione su Hyper-V (anche l'host è 2008 R2 x64); la macchina virtuale ha 4,2 GB di memoria fisica utilizzata su 5 GB e 4,6 GB di commit su un massimo di 10 GB; l'host ha 7,2 GB di memoria fisica utilizzata su 8 GB e 7,8 commit su 16 GB max. Entrambe le macchine sono più lente a causa dell'utilizzo dell'HD ma non sono ostruite.
GFK

Risposte:


0
  1. Stick con il comando UPDATE. CURSOR sarà più lento per quello che stai cercando di fare.
  2. Elimina / disabilita tutti gli indici, inclusi quelli per le viste indicizzate. Se si dispone di chiave esterna su AX, rilasciarla.
  3. Crea un indice che conterrà solo A.B_ID e un altro per B.ID.
  4. Anche se si utilizza il modello di recupero semplice, l'ultima transazione sarà sempre nel registro delle transazioni prima di essere scaricata sul disco. Ecco perché è necessario pre-crescere il registro delle transazioni e impostarlo in modo che cresca per un importo maggiore (ad es. 100 MB).
  5. Inoltre, impostare la crescita del file di dati su un importo maggiore.
  6. Assicurarsi di disporre di spazio su disco sufficiente per un'ulteriore crescita di file di registro e dati.
  7. Al termine dell'aggiornamento, ricrea / abilita gli indici che hai lasciato cadere / disabilitato nel passaggio 2.
  8. Se non sono più necessari, rilasciare gli indici creati nel passaggio 3.

Modifica: Dal momento che non posso commentare il tuo post originale, risponderò qui alla tua domanda da Modifica 4. Hai 7 indici su AX Index è un albero B , e ogni aggiornamento a quel campo provoca il ribilanciamento dell'albero B. È più veloce ricostruire quegli indici da zero che riequilibrarli ogni volta.


Per il punto 1 vedi la mia risposta a ik_zelf. Il cursore è lì per motivi di indagine e non ha molto impatto. Intendo implementare il resto dei tuoi suggerimenti, penso che sia tutto ciò che mi resta da fare. Se funziona, rimarrò comunque senza una spiegazione di ciò che accade ora ...
GFK

Puoi pubblicare DDL per le tue tabelle (inclusi tutti gli indici, i vincoli, ecc.). Forse c'è qualcosa che rallenta la tua esibizione e te ne stai perdendo.
Bojan,

1
Elimina indici / Aggiorna / Ricostruisci indici funziona, e anche se preferirei non dover fare qualcosa di così drastico, non vedo che ho una scelta. Grazie!
GFK

0

Una cosa da guardare sono le risorse di sistema (memoria, disco, CPU) durante questo processo. Ho tentato di inserire 7 milioni di singole righe in una singola tabella in un unico grande lavoro e il mio server si è bloccato in un modo simile al tuo.

Si scopre che non avevo memoria sufficiente sul mio server per eseguire questo processo di inserimento di massa. In situazioni come questa, a SQL piace tenere la memoria e non lasciarla andare .... anche dopo che detto comando di inserimento può o non può essere completato. Più comandi vengono elaborati in lavori di grandi dimensioni, più memoria viene consumata. Un rapido riavvio ha liberato la memoria.

Quello che vorrei fare è avviare questo processo da zero con il Task Manager in esecuzione. Se l'utilizzo della memoria supera il 75%, le possibilità del tuo sistema / processo di congelare i razzi astronomicamente.

Se la tua memoria / risorse sono davvero limitate come notato sopra, allora le tue opzioni sono di tagliare il processo in pezzi più piccoli (con il riavvio occasionale se l'utilizzo della memoria è elevato) invece di un grosso lavoro o passare a un server a 64 bit con molta memoria.


0

Lo scenario di aggiornamento è sempre più rapido rispetto all'utilizzo di una procedura.

Dato che stai aggiornando la colonna X di tutte le righe nella tabella A, assicurati di rilasciare prima l'indice su quella. Assicurati inoltre che non vi siano elementi come trigger e vincoli attivi su quella colonna.

L'aggiornamento degli indici è un'attività costosa, così come la convalida dei vincoli e l'esecuzione di trigger a livello di riga che eseguono una ricerca in altri dati.


Non credo sia questo il punto. Mi rendo conto che l'aggiornamento dei record indicizzati richiede tempo, e so che, nel complesso, parte del tempo necessario è dovuto a questo. Ma mi aspetto questo, e sto bene con esso: come ho detto, l'aggiornamento del 99% delle righe richiede 5 minuti (anche usando il cursore), ma per qualche motivo, una riga (e non sempre la stessa) richiede 5 ore. Ciò che mi preoccupa è questo comportamento particolare.
GFK

locks non è un problema che hai detto .... che ne pensi dell'utilizzo del filesystem, raggiungendo il 90% o più?
ik_zelf,

no, è 31 GB gratuito su 120 GB, quindi penso che sia ok
GFK

cosa succede se si tenta di copiare la tabella come creare la tabella a_copy come selezionare * da a;
ik_zelf,
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.