SQL: Cosa sta rallentando gli INSERT se non CPU o IO?


19

Abbiamo un database per un prodotto che è pesante per la scrittura. Abbiamo appena acquistato un nuovo server con un SSD per aiutare. Con nostra sorpresa, gli inserimenti non sono stati più veloci rispetto alla nostra vecchia macchina con una memoria molto più lenta. Durante il benchmarking abbiamo notato che la velocità di I / O mostrata dal processo di SQL Server era molto bassa.

Ad esempio, ho eseguito lo script trovato in questa pagina , tranne per il fatto che ho aggiunto BEGIN TRAN e COMMIT attorno al loop. Nel migliore dei casi ho visto l'utilizzo del disco raggiungere i 7 Mb / s, mentre la CPU ha toccato appena il 5%. Il server ha 64 GB installati e sta usando 10. Il tempo di esecuzione totale è stato di 2 minuti e 15 secondi per la prima chiamata fino a circa 1 minuto per le chiamate successive. Il database è in fase di recupero semplice ed era inattivo durante il test. Ho lasciato cadere il tavolo tra ogni chiamata.

Perché una sceneggiatura così semplice è così lenta? L'hardware non viene quasi mai usato. Entrambi gli strumenti dedicati di benchmarking del disco e SQLIO indicano che l'SSD funziona correttamente con velocità superiori a 500 Mb / s sia in lettura che in scrittura. Capisco che le scritture casuali sono più lente delle scritture sequenziali, ma mi aspetterei che un semplice inserimento come questo, su una tabella senza indicizzazione in cluster, sia molto più veloce.

Alla fine il nostro scenario è molto più complesso, ma sento che devo prima capire un caso semplice. In breve, la nostra applicazione elimina i vecchi dati, quindi utilizza SqlBulkCopy per copiare nuovi dati nelle tabelle di gestione temporanea, esegue alcuni filtri e infine utilizza MERGE e / o INSERT INTO a seconda dei casi per copiare i dati nelle tabelle finali.

-> EDIT 1: ho seguito la procedura collegata da Martin Smith e ho ottenuto il seguente risultato:

[Wait Type]  [Wait Count] [Total Wait (ms)] [T. Resource Wait (ms)] [T. Signal Wait (ms)]
NETWORK_IO          5008              46735                 46587        148
LOGBUFFER           901               5994                  5977         17
PAGELATCH_UP        40                866                   865          1
SOS_SCHEDULER_YIELD 53279             219                   121          98
WRITELOG            5                 145                   145          0
PAGEIOLATCH_UP      4                 58                    58           0
LATCH_SH            5                 0                     0            0

Trovo strano che NETWORK_IO richieda la maggior parte del tempo, considerando che non ci sono risultati da visualizzare e nessun dato da trasferire altrove che nei file SQL. Il tipo NETWORK_IO include tutti gli IO?

-> EDIT 2: ho creato un disco RAM da 20 Gb e da lì ho montato un database. Il miglior tempo che ho avuto sull'SSD è di 48 secondi, con il disco RAM che è sceso a 37 secondi. NETWORK_IO è ancora l'attesa più grande. La massima velocità di scrittura sul disco RAM era di circa 250 Mb / s mentre è in grado di eseguire più gigabyte al secondo. Non utilizzava ancora molta CPU, quindi cosa sta trattenendo SQL?



3
il NETWORK_IOpotrebbe essere dal "1 row (s) affected" 3 milioni di messaggi inviati indietro. Hai provato ad aggiungere SET NOCOUNT ONallo script?
Martin Smith,

Sì, ho aggiunto NOCOUNT.
Djof

2
Strano. Allora non mi aspetto molto dalle attività di rete. Hai eliminato i vecchi file degli eventi estesi tra le esecuzioni? Lo script che li legge utilizza un jolly in EE_WaitStats*.xelmodo che quelli vecchi contaminino i tuoi risultati.
Martin Smith,

Buona chiamata, aggiornerò i risultati domani.
Djof

Risposte:


9

So che è una vecchia domanda, ma questo potrebbe ancora aiutare gli utenti ed è un problema che si presenta di tanto in tanto.

Il motivo principale per cui stai raggiungendo un limite di prestazioni senza vedere alcun collo di bottiglia delle risorse è perché hai raggiunto il limite di ciò che è possibile elaborare in un singolo thread di sessione. Il ciclo non viene elaborato in parallelo, ma tutti gli inserti vengono eseguiti in serie.

Nel mio caso, ci vogliono 36 secondi per inserire 3 milioni di righe. Ciò significa 36/30000000 = 0,000012 secondi per riga. È abbastanza veloce. Sul mio sistema, sono sufficienti 0,000012 per eseguire tutti i passaggi necessari.

L'unico modo per farlo più velocemente è avviare una seconda sessione in parallelo.

Se inizio 2 sessioni in parallelo, eseguo entrambe 15 milioni di inserimenti. Entrambi finiscono in 18 secondi. Potrei scalare di più, ma la mia attuale configurazione di test sta raggiungendo il 95% della CPU con due sessioni parallele, quindi fare 3 distorcerebbe i risultati poiché colpirei un collo di bottiglia della CPU.

Se inizio 2 sessioni parallele inserendo entrambe 3 milioni di righe, entrambe terminano in 39 secondi. quindi ora sono 6 milioni di righe in 39 secondi.

Ok, questo ci lascia ancora con l'attesa NETWORK_IO di presentarci.

Le attese NETWORK_IO vengono aggiunte dal fatto che si stanno utilizzando eventi estesi per rintracciarli. Nel mio caso l'inserto impiega 36 secondi (su avg). Quando si utilizza il modo evento esteso (dal collegamento sopra nel primo commento) questo è ciò che è registrato:

Wait Type             Wait Count  Total Wait Time (ms) Total Resource Wait Time (ms) Total Signal Wait Time (ms)
NETWORK_IO            3455        68808                68802                         6
PAGEIOLATCH_SH        3           64                   64                            0
PAGEIOLATCH_UP        12          58                   58                            0
WRITE_COMPLETION      8           15                   15                            0
WRITELOG              3           9                    9                             0
PAGELATCH_UP          2           4                    4                             0
SOS_SCHEDULER_YIELD   32277       1                    0                             1
IO_COMPLETION         8           0                    0                             0
LATCH_SH              3           0                    0                             0
LOGBUFFER             1           0                    0                             0

Puoi vedere che sono registrati 68 secondi di NETWORK_IO. Ma poiché il ciclo di inserimento è una singola azione con thread che ha richiesto 36 secondi, questo non può essere. (Sì, vengono utilizzati più thread, ma le operazioni sono seriali, mai in parallelo, quindi non è possibile accumulare più tempo di attesa rispetto alla durata totale della query)

Se non utilizzo eventi estesi ma solo i DMV delle statistiche di attesa in un'istanza silenziosa (con solo io che eseguo l'inserimento) ottengo questo:

Wait Type                   Wait Count  Total Wait Time (ms)  Total Resource Wait Time (ms) Signal Resource Wait Time (ms)
SOS_SCHEDULER_YIELD             8873                 0.21                                    0.01                                    0.20
PAGEIOLATCH_UP                  3                    0.02                                    0.02                                    0.00
PREEMPTIVE_OS_AUTHENTICATIONOPS 17                   0.02                                    0.02                                    0.00
PAGEIOLATCH_SH                  1                    0.00                                    0.00                                    0.00

Quindi NETWORK_IO che stavi vedendo nel registro degli eventi estesi non era correlato al tuo ciclo di inserimento. (Se non accendessi nocount, avresti un'enorme rete asincrona in attesa di IO, +1 Martin)

Tuttavia, non so perché NETWORK_IO venga visualizzato nella traccia eventi estesa. Sicuramente la scrittura su un target di file asincrono degli eventi accumula ASYNC_NETWORK_IO, ma sicuramente questo viene fatto su un SPID diverso da quello su cui stiamo filtrando. Potrei farmi questa domanda come una nuova domanda)


1
"stai raggiungendo un limite di prestazioni senza vedere alcun collo di bottiglia delle risorse è perché hai raggiunto il limite di ciò che è possibile elaborare in un singolo thread di sessione": stai descrivendo un collo di bottiglia della CPU al 100% (su un core). Se non c'è collo di bottiglia, il sistema sarà più veloce, in modo da qualcosa altro deve essere in gioco.
Remus Rusanu,

La tua risposta è Edward molto istruttiva. Sembra che il parallelismo sia la soluzione al nostro problema a cui stiamo già lavorando, anche se richiede modifiche al layout del nostro database. Come Remus, tuttavia, sono ancora curioso di sapere perché la macchina non sembra utilizzare tutte (di una) CPU o risorse del disco.
Djof,

9

In genere si inizia guardando sys.dm_exec_requests, in particolare wait_time, wait_typee wait_resourceper le richieste INSERT. Ciò fornirà una chiara indicazione di ciò che sta bloccando INSERT. I risultati indicheranno se si tratta di contesa di blocco, eventi di crescita dei file, attese di svuotamento del registro, contesa di allocazione (si manifesta come contesa di blocco della pagina PFS) ecc ecc. Dopo aver misurato, aggiornare la domanda di conseguenza. Vi esorto caldamente a fermarvi ora e leggere la metodologia di risoluzione dei problemi di attese e code prima di procedere.


3

Ho eseguito lo script di test nella pagina collegata nell'OP con BEGIN TRAN / COMMIT attorno al loop. Sulla mia macchina, ci sono voluti 1:28 per completare la prima volta.

Quindi ho spostato questi due comandi fuori dal ciclo:

SELECT @Random = ROUND(((@Upper - @Lower -1) * RAND() + @Lower), 0)
SET @InsertDate = DATEADD(dd, @Random, GETDATE())

Si è completato dopo 28 secondi.

Non so per certo cosa stia succedendo, ma immagino che nel RAND()codice potrebbe esserci un sonno di qualche tipo , forse come parte dell'algoritmo che stanno usando per generare entropia (migliori numeri casuali).

FWIW, gli SSD non sono sempre la migliore tecnologia per le app pesanti. Per prestazioni ottimali, assicurarsi che il registro DB sia su una lettera di unità diversa rispetto ai dati DB, che il file di registro sia pre-cresciuto alla sua dimensione massima e non troncare mai il registro.


Grazie per il tuo contributo RickNZ. Non ho ottenuto risultati più rapidi spostando il codice fuori dal ciclo. Aspetta che ho osservato è che se lo esegui più volte diventa più veloce, potrebbe essere quello che hai vissuto. So che gli SSD non sono proiettili d'argento, ma sento ancora che le prestazioni non sono quelle che potrebbero essere.
Djof

1

Un altro DMV che utilizzo per identificare la lentezza è sys.dm_os_waiting_tasks . Se la tua query non richiede molta CPU, puoi trovare maggiori informazioni sulle attese da questo DMV.


0

Sto controllando l'elenco degli eventi di attesa per sql 2008 e non vedo NETWORK_IO elencato: http://technet.microsoft.com/en-us/library/ms179984(v=sql.100).aspx

Pensavo che NETWORK_IO fosse ora elencato come ASYNC_NETWORK_IO, quindi volevo chiedere se potevi controllare di nuovo la tua versione di SQL, perché sono semplicemente curioso di sapere come / perché quell'evento di attesa appare per quella versione.

Per quanto riguarda l'attesa della rete, sì, può succedere anche se stai lavorando su un server autonomo. Hai controllato le impostazioni per le tue schede di rete? Mi chiedo se sono un problema.

Alla fine della giornata ci sono solo alcuni colli di bottiglia delle risorse: memoria, CPU, I / O del disco, rete e blocco. Hai indicato che CPU e I / O non sono il problema e hai un evento di attesa di NETWORK_IO, quindi ti suggerisco di guardare prima quelle schede NIC.


1
La NETWORK_IOviene visualizzata perché l'OP utilizza eventi estesi. Non è mai stato aggiornato insys.dm_xe_map_values
Martin Smith il

Sto pensando lo stesso SQLRockstar, proprio quello che potrebbe succedere. Ho provato a disabilitare completamente le schede di rete. Martin ha sottolineato che alcuni vecchi file potrebbero essere ancora lì. Aggiornerò i risultati domani per vedere se cambia qualcosa.
Djof

inoltre, potrebbe essere utile vedere i piani di esecuzione delle dichiarazioni.
SQLRockstar,
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.