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).