Per contare il numero di righe con una data specifica, MySQL deve individuare quel valore nell'indice (che è abbastanza veloce, dopotutto è per questo che sono fatti gli indici) e poi leggere le voci successive dell'indice finché non trova la data successiva. A seconda del tipo di dati di esi
, questo si riassumerà nella lettura di alcuni MB di dati per contare le tue 700.000 righe. La lettura di alcuni MB non richiede molto tempo (e quei dati potrebbero anche essere già memorizzati nella cache nel pool di buffer, a seconda della frequenza con cui utilizzi l'indice).
Per calcolare la media per una colonna che non è inclusa nell'indice, MySQL utilizzerà, ancora una volta, l'indice per trovare tutte le righe per quella data (come prima). Ma inoltre, per ogni riga trovata, deve leggere i dati della tabella effettivi per quella riga, il che significa utilizzare la chiave primaria per individuare la riga, leggere alcuni byte e ripetere 700.000 volte. Questo "accesso casuale"
è molto più lento della lettura sequenziale nel primo caso. (Ciò peggiora con il problema che "alcuni byte" sono innodb_page_size
(16 KB per impostazione predefinita), quindi potresti dover leggere fino a 700.000 * 16 KB =11 GB, rispetto a "alcuni MB" per count(*)
; e, a seconda della configurazione della memoria, alcuni di questi dati potrebbero non essere memorizzati nella cache e devono essere letti dal disco.)
Una soluzione a questo è includere tutte le colonne utilizzate nell'indice (un "indice di copertura"), ad es. crea un indice su date, 01
. Quindi MySQL non ha bisogno di accedere alla tabella stessa e può procedere, in modo simile al primo metodo, semplicemente leggendo l'indice. La dimensione dell'indice aumenterà leggermente, quindi MySQL dovrà leggere "qualche MB in più" (ed eseguire il avg
-operazione), ma dovrebbe essere ancora una questione di secondi.
Nei commenti, hai menzionato che devi calcolare la media su 24 colonne. Se vuoi calcolare la avg
per più colonne contemporaneamente, avresti bisogno di un indice di copertura su tutte, ad es. date, 01, 02, ..., 24
per impedire l'accesso al tavolo. Tieni presente che un indice che contiene tutte le colonne richiede tanto spazio di archiviazione quanto la tabella stessa (e ci vorrà molto tempo per creare un tale indice), quindi potrebbe dipendere dall'importanza di questa query se vale quelle risorse.
Per evitare il limite MySQL di 16 colonne per indice
, potresti dividerlo in due indici (e due query). Crea ad es. gli indici date, 01, .., 12
e date, 13, .., 24
, quindi usa
select * from (select `date`, avg(`01`), ..., avg(`12`)
from mytable where `date` = ...) as part1
cross join (select avg(`13`), ..., avg(`24`)
from mytable where `date` = ...) as part2;
Assicurati di documentarlo bene, poiché non vi è alcuna ragione ovvia per scrivere la query in questo modo, ma potrebbe valerne la pena.
Se fai la media solo su una singola colonna, puoi aggiungere 24 indici separati (su date, 01
, date, 02
, ...), anche se in totale richiederanno ancora più spazio, ma potrebbero essere un po' più veloci (poiché sono più piccoli singolarmente). Ma il pool di buffer potrebbe comunque favorire l'indice completo, a seconda di fattori come i modelli di utilizzo e la configurazione della memoria, quindi potrebbe essere necessario testarlo.
Dal date
fa parte della tua chiave primaria, potresti anche considerare di cambiare la chiave primaria in date, esi
. Se trovi le date in base alla chiave primaria, non avrai bisogno di un passaggio aggiuntivo per accedere ai dati della tabella (poiché accedi già alla tabella), quindi il comportamento sarebbe simile all'indice di copertura. Ma questa è una modifica significativa alla tua tabella e può influenzare tutte le altre query (che ad esempio usano esi
per individuare le righe), quindi deve essere considerato attentamente.
Come hai detto, un'altra opzione sarebbe quella di creare una tabella di riepilogo in cui memorizzi valori precalcolati, soprattutto se non aggiungi o modifichi righe per date passate (o puoi tenerle aggiornate con un trigger).