Ottimizzazione della condizione WHERE per il campo TIMESTAMP nell'istruzione SELECT di MySQL


8

Sto lavorando a uno schema per un sistema di analisi che tiene traccia dei tempi di utilizzo e vi è la necessità di vedere il tempo totale di utilizzo in un determinato intervallo di date.

Per fare un semplice esempio, questo tipo di query verrebbe eseguito spesso:

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Questa query richiede in genere circa 7 secondi su una tabella fortemente popolata. Ha ~ 35 milioni di righe, MyISAM su MySQL in esecuzione su Amazon RDS (db.m3.xlarge).

Sbarazzarsi della clausola WHERE fa sì che la query richieda solo 4 secondi e l'aggiunta di una seconda clausola (time_off> XXX) aggiunge altri 1,5 secondi, portando il tempo di query a 8,5 secondi.

Dal momento che so che questi tipi di query verranno comunemente eseguiti, vorrei ottimizzare le cose in modo che siano più veloci, idealmente inferiori a 5 secondi.

Ho iniziato aggiungendo un indice su time_on, e sebbene questo abbia velocizzato drasticamente una query WHERE "=", non ha avuto alcun effetto sulla query ">". Esiste un modo per creare un indice che acceleri le query WHERE ">" o "<"?

O se ci sono altri suggerimenti sulle prestazioni di questo tipo di query, per favore fatemelo sapere.

Nota: sto usando il campo "diff_ms" come fase di denormalizzazione (equivale a time_off - time_on) che migliora le prestazioni dell'aggregazione di circa il 30% -40%.

Sto creando l'indice con questo comando:

ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;

L'esecuzione di "spiegazione" sulla query originale (con "time_on>") dice che time_on è un "possible_key" e il tipo di selezione è "SEMPLICE". La colonna "extra" dice "Utilizzo di where" e "type" è "ALL". Dopo aver aggiunto l'indice, la tabella indica che "time_on" è il tipo di chiave "MUL", che sembra corretto poiché lo stesso tempo può essere presente due volte.

Ecco lo schema della tabella:

CREATE TABLE `writetest_table` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sessionID` int(11) DEFAULT NULL,
  `time_on` timestamp NULL DEFAULT NULL,
  `time_off` timestamp NULL DEFAULT NULL,
  `diff_ms` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `time_on` (`time_on`)
) ENGINE=MyISAM AUTO_INCREMENT=50410902 DEFAULT CHARSET=latin1;

AGGIORNAMENTO: ho creato il seguente indice in base alla risposta di ypercube, ma ciò aumenta il tempo di interrogazione per la prima interrogazione a circa 17 secondi!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;

AGGIORNAMENTO 2: output EXPLAIN

mysql> explain select sum(diff_ms) from writetest_table where time_on > '2015-07-13 15:11:56';
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| id | select_type | table               | type  | possible_keys        | key                  | key_len | ref  | rows     | Extra                    |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | writetest_table_old | index | time_on__diff_ms__ix | time_on__diff_ms__ix | 10      | NULL | 35831102 | Using where; Using index |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)

Aggiornamento 3: risultato della query richiesta

mysql> SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
+---------------------+
| time_on             |
+---------------------+
| 2015-07-13 15:11:56 |
+---------------------+
1 row in set (0.01 sec)

Hai effettivamente null in queste 2 colonne ( time_one diff_ms)? Cosa succede se aggiungi la query WHERE ... AND diff_ms IS NOT NULL?
ypercubeᵀᴹ

Potete per favore mostrarci l'output diSELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
ypercubeᵀᴹ

Anche la spiegazione nel tuo "Aggiornamento 2" mostra " tabella:writetest_table_old " mentre la query ha from writetest_table. È un refuso o si esegue la query in una tabella diversa?
ypercubeᵀᴹ

Risposte:


3

Penso che sto iniziando a capire.

Quando ti ho chiesto di scappare

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

Hai detto che era quello 2015-07-13 15:11:56che hai nella tua WHEREclausola

Quando hai fatto la query

select sum(diff_ms) from writetest_table;

Ha eseguito una scansione completa della tabella di 35,8 milioni di righe.

Quando hai fatto la query

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

Ha eseguito una scansione dell'indice completo di 35,8 milioni di righe.

Ha perfettamente senso che la query senza la clausola WHERE sia più veloce. Perché ?

La scansione della tabella legge 35,8 milioni di righe in un passaggio lineare.

EXPLAIN sulla query con WHERE ha anche generato 35,8 milioni di righe. Una scansione dell'indice si comporterebbe in modo leggermente diverso. Mentre il BTREE mantiene l'ordine delle chiavi, è orribile per eseguire scansioni di portata. Nel tuo caso particolare, stai eseguendo la scansione dell'intervallo peggiore possibile, che avrebbe lo stesso numero di voci BTREE quante sono le righe nella tabella. MySQL deve attraversare le pagine BTREE (almeno attraverso i nodi foglia) per leggere i valori. Inoltre, la time_oncolonna deve essere confrontata lungo la strada nell'ordine dettato dall'indice. Pertanto, anche i nodi BTREE non foglia devono essere attraversati.

Si prega di consultare i miei post su BTREEs

Se la query era a partire da mezzanotte oggi

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

o anche mezzogiorno oggi

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

dovrebbe richiedere meno tempo.

MORALE DELLA STORIA: non utilizzare una clausola WHERE che esegua una scansione dell'intervallo ordinato pari al numero di righe nella tabella di destinazione.


Il mio unico problema è come andare da qui. Ho fatto una query con una data che ha prodotto solo 1 milione di righe filtrate e la somma ha richiesto solo 1 secondo. Ma a volte potrei dover fare somme aggregate nella maggior parte dei dati. Qualche suggerimento su come gestirlo? Speravo che MySQL fosse abbastanza intelligente da sapere quando usare l'indice e quando no, ma suppongo che non abbia abbastanza informazioni in questo caso.
Locksleyu,

Vorrei davvero che ci fosse una sorta di indice organizzato per rendere veloci le clausole WHERE che specificano gli intervalli di date, che sembra tecnicamente possibile implementare, ma immagino che non sia supportato.
Locksleyu,

Hai troppi dati in un intervallo così breve. Nessuna clausola WHERE può mai essere compensata. Perché ? Non è l'indice il problema. È l'opinione dell'indice di MySQL Query Optimizer sull'indice. Quando inizi ad accumulare molti più dati (diciamo per circa due settimane), le statistiche dell'indice dovrebbero stabilizzarsi e dovresti vedere un miglioramento delle prestazioni. Basta non eseguire scansioni dell'indice complete.
RolandoMySQLDBA

4

Per la query specifica:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

un indice su (time_on, diff_ms)sarebbe l'opzione migliore. Quindi, se la query viene eseguita abbastanza spesso o la sua efficienza è cruciale per la tua applicazione, aggiungi questo indice:

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(Non correlato alla domanda)
E davvero, cambia il motore della tabella in InnoDB. È il 2015 e il funerale di MyISAM è stato alcuni anni fa.
(/ rant)


Ho creato l'indice esatto che hai suggerito e quindi ho eseguito la query esatta che hai menzionato per prima nella tua risposta, ma il tempo è ora molto peggio, impiegando circa 17 secondi in modo coerente (ho provato più volte).
Locksleyu,

Non ho idea di cosa lo stia causando. Nel caso in cui sia importante, ci sono solo 3671 valori distinti di time_on nella tabella (questo è dovuto al modo in cui il mio script di test sta popolando i dati).
Locksleyu,

Dovresti fare tre (3) cose: 1. run ALTER TABLE writetest_table DROP INDEX time_on;, 2) run ANALYZE TABLE writetest_table;e 3) riesegui la query. Il tempo torna a 7 secondi?
RolandoMySQLDBA

1
Dovresti anche correre EXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");. Il nuovo indice è in uso? Se non viene utilizzato, direi che è la tua popolazione chiave, soprattutto se il tuo primo time_on è solo pochi giorni fa. Poiché il numero di righe aumenta con giorni più distinti, la distribuzione delle chiavi dovrebbe stabilizzarsi e EXPLAIN dovrebbe essere migliore .
RolandoMySQLDBA

RolandoMySQLDBA - Ho provato i tuoi tre passaggi e sì, il tempo torna indietro a 7 secondi. Ho spiegato e dice che l'indice è in uso. Sono ancora all'oscuro del perché l'aggiunta di un indice come questo potrebbe peggiorare le prestazioni rispetto al doppio.
Locksleyu,
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.