Raggruppa per ora su set di dati di grandi dimensioni


12

Usando MS SQL 2008 sto selezionando un campo medio da 2,5 milioni di record. Ogni record rappresenta un secondo. MyField è una media oraria di quei record da 1 secondo. Naturalmente la CPU del server raggiunge il 100% e la selezione richiede troppo tempo. Devo eventualmente salvare quei valori medi in modo che SQL non debba selezionare tutti quei record su ogni richiesta. Cosa si può fare?

  SELECT DISTINCT
         CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp

6
TimeStamp fa parte di un indice cluster? Dovrebbe essere ...

@antisanity - perché? sta esaurendo la CPU e non il disco io
Jack dice che prova topanswers.xyz

Risposte:


5

La parte della query sta massimizzando la CPU per lunghi periodi sono le funzioni nella clausola GROUP BY e il fatto che il raggruppamento richiederà sempre un ordinamento non indicizzato in questa istanza. Mentre un indice sul campo timestamp aiuterà il filtro iniziale, questa operazione deve essere eseguita su ogni riga corrispondente al filtro. Accelerare questo sta usando un percorso più efficiente per fare lo stesso lavoro suggerito da Alex, ma avrai ancora un'enorme inefficienza lì perché qualsiasi combinazione di funzioni che usi il pianificatore di query non sarà in grado di inventare qualcosa che sarà aiutato da qualsiasi indice, quindi dovrà passare attraverso ogni riga prima di eseguire le funzioni per calcolare i valori di raggruppamento, solo allora può ordinare i dati e calcolare gli aggregati sui raggruppamenti risultanti.

Quindi la soluzione è in qualche modo creare il gruppo di processi in base a qualcosa per cui può utilizzare un indice, o altrimenti rimuovere la necessità di considerare tutte le righe corrispondenti contemporaneamente.

È possibile mantenere una colonna aggiuntiva per ogni riga contenente il tempo arrotondato all'ora e indicizzare questa colonna per l'utilizzo in tali query. Questo sta denormalizzando i tuoi dati, quindi potrebbe sembrare "sporco", ma funzionerebbe e sarebbe più pulito della memorizzazione nella cache di tutti gli aggregati per un uso futuro (e l'aggiornamento di quella cache quando i dati di base vengono modificati). La colonna aggiuntiva deve essere gestita da trigger o essere una colonna calcolata persistente, piuttosto che mantenuta dalla logica altrove, poiché ciò garantirà tutti i luoghi attuali e futuri che potrebbero inserire dati o aggiornare le colonne data / ora o le righe esistenti comportando dati coerenti nel nuovo colonna. Puoi comunque ottenere il MIN (timestamp). Ciò che la query produrrà in questo modo è ancora una passeggiata lungo tutte le righe (questo non può essere evitato, ovviamente) ma può farlo indicizzando l'ordine, emettere una riga per ogni raggruppamento man mano che arriva al valore successivo nell'indice anziché dover ricordare l'intera serie di righe per un'operazione di ordinamento non indicizzata prima di poter eseguire il raggruppamento / aggregazione. Utilizzerà anche molta meno memoria, poiché non sarà necessario ricordare le righe dei precedenti valori di raggruppamento per elaborare quella che sta guardando ora o il resto di esse.

Tale metodo rimuove la necessità di trovare da qualche parte nella memoria l'intero set di risultati e fa l'ordinamento non indicizzato per l'operazione di gruppo e rimuove il calcolo dei valori di gruppo dalla query grande (spostando quel lavoro nei singoli INSERT / UPDATE che producono il dati) e dovrebbe consentire l'esecuzione di tali query in modo accettabile senza la necessità di mantenere un archivio separato dei risultati aggregati.

Un metodo che non funzionadenormalizzare i dati, ma richiede comunque una struttura aggiuntiva, consiste nell'utilizzare una "tabella oraria", in questo caso una contenente una riga all'ora per tutto il tempo che è probabile prendere in considerazione. Questa tabella non consumerebbe una quantità significativa di spazio in un DB o dimensioni apprezzabili - per coprire un periodo di 100 anni una tabella contenente una riga di due date (l'inizio e la fine dell'ora, come "01-01-2011 @ 00: 00: 00.0000 ',' 2011-01-01 @ 00: 00: 59.9997 ', il "9997" è il numero più piccolo di millisecondi che un campo DATETIME non arrotonda al secondo successivo) che fanno entrambi parte del la chiave primaria in cluster richiederà ~ 14 MB di spazio (8 + 8 byte per riga * 24 ore / giorno * 365,25 giorni / anno * 100, più un po 'per l'overhead della struttura ad albero dell'indice cluster ma quell'overhead non sarà significativo) .

SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
     , MIN([timestamp]) as TimeStamp
     , AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime

Ciò significa che il pianificatore di query può disporre dell'indice su MyData.TimeStamp da utilizzare. Il pianificatore di query dovrebbe essere abbastanza luminoso da capire che può camminare lungo la tabella addomesticata al passo con l'indice su MyData.TimeStamp, emettendo di nuovo una riga per raggruppamento e scartando ogni set o righe quando raggiunge il successivo valore di raggruppamento. Non è possibile memorizzare tutte le righe intermedie da qualche parte nella RAM, quindi eseguire un ordinamento non indicizzato su di esse. Naturalmente questo metodo richiede che tu crei la tabella dei tempi e assicurati che si estenda abbastanza avanti e indietro, ma puoi usare la tabella dei tempi per le query su molti campi della data in query diverse, dove l'opzione "colonna aggiuntiva" richiederebbe una colonna calcolata extra per ciascun campo data che è necessario filtrare / raggruppare in questo modo e le dimensioni ridotte della tabella (a meno che non sia necessario che si estenda su 10,

Il metodo della tabella dei tempi presenta una differenza aggiuntiva (che potrebbe essere abbastanza vantaggiosa) rispetto alla situazione attuale e alla soluzione di colonna calcolata: può restituire righe per periodi per i quali non vi sono dati, semplicemente modificando INNER JOIN nella query di esempio sopra di essere ESTERNO SINISTRA.

Alcune persone suggeriscono di non avere una tabella dei tempi fisica, ma invece di restituirla sempre da una funzione di restituzione della tabella. Ciò significa che il contenuto della tabella dei tempi non viene mai archiviato sul disco (o deve essere letto da) e se la funzione è ben scritta non devi mai preoccuparti di quanto tempo la tabella dei tempi deve andare avanti e indietro nel tempo, ma io dubbio il costo della CPU di produrre una tabella in memoria per alcune righe ogni domanda vale il piccolo risparmio di fastidio di creare (e mantenere, deve il suo bisogno periodo di estendere oltre il limite della vostra versione iniziale) la tabella tempo fisico.

Una nota a margine: non è nemmeno necessaria la clausola DISTINCT sulla query originale. Il raggruppamento assicurerà che queste query restituiscano solo una riga per periodo in esame, quindi DISTINCT non farà altro che girare un po 'di più la CPU (a meno che il pianificatore di query non noti che il distinto sarebbe una no-op nel qual caso lo farà ignoralo e non usare altro tempo CPU).


3

Vedi questa domanda ( floor a date ) Inoltre, perché preoccuparsi di convertire tutto in stringa - puoi farlo in seguito (se necessario).

  SELECT DISTINCT
         dateadd(hour,datediff(hour,0,[timestamp]),0) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY dateadd(hour,datediff(hour,0,[timestamp],0);
ORDER BY TimeStamp

1

Vuoi rendere la query più veloce o stai chiedendo come creare un'istantanea dei dati e salvarla?

Se vuoi renderlo più veloce, hai sicuramente bisogno di un indice sul campo TimeStamp. Inoltre, suggerirei di utilizzare questo per convertire in ora:

select convert(varchar(13), getdate(), 121)

Se è necessario creare un'istantanea e riutilizzarla in seguito, utilizzare insert intoper creare una nuova tabella con i risultati della query. Tabella indice secondo e usarlo. Da quello che ho capito avrai bisogno di un indice su TimeStampHour.

Inoltre è possibile impostare un lavoro che aggrega i dati giornalieri nella nuova tabella aggregata.


-1

Convertendo il tuo gruppo per clausola in una stringa del genere, lo stai essenzialmente trasformando in un hit non indicato per ogni singola riga nel database. Questo è ciò che sta uccidendo la tua performance. Qualsiasi server decente a metà strada sarà in grado di gestire un semplice aggregato come quello su un milione di record bene se gli indici vengono utilizzati correttamente. Vorrei modificare la tua richiesta e inserire un indice cluster nei tuoi timestamp. Questo risolverà il tuo problema di prestazioni, mentre il calcolo dei dati ogni ora sta solo rimandando il problema.


1
-1 - no non stai "rendendo un hit non indicizzato per ogni singola riga nel database" - qualsiasi indice TimeStampattivo verrà comunque utilizzato per filtrare le righe
Jack dice che prova topanswers.xyz

-3

Vorrei considerare l'idea di abbandonare l'idea di implementare questo tipo di calcolo usando un modello di database relazionale. Soprattutto se si hanno molti punti dati per i quali si raccolgono valori ogni secondo.

Se hai i soldi, potresti prendere in considerazione l'acquisto di uno storico dei dati di processo dedicato come:

  1. PHD di Honeywell Uniformance
  2. Osisoft PI
  3. Aspentech IP21
  4. eccetera.

Questi prodotti sono in grado di memorizzare enormi quantità di dati di serie temporali follemente densi (in formati proprietari), consentendo allo stesso tempo una rapida elaborazione delle query di estrazione dei dati. Le query possono specificare molti punti dati (chiamati anche tag), intervalli di tempo lunghi (mesi / anni) e possono inoltre eseguire un'ampia varietà di calcoli di dati di riepilogo (comprese le medie).

.. e in generale: cerco sempre di evitare di usare la DISTINCTparola chiave quando scrivo SQL. Non è quasi mai una buona idea. Nel tuo caso dovresti essere in grado di eliminare DISTINCTe ottenere gli stessi risultati aggiungendo MIN([timestamp])alla tua GROUP BYclausola.


1
Questo non è molto preciso. Un database relazionale va benissimo per 2,5 milioni di record. E non sta nemmeno facendo join su molti tavoli. La prima indicazione che è necessario denormalizzare i dati o passare a un sistema non relazionale è quando si eseguono join complessi e di grandi dimensioni su più tabelle. Il set di dati del poster in realtà sembra un uso perfettamente accettabile di un sistema di database relazionale.
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.