2009-08-26 22 views
39

Supponiamo, a scopo illustrativo, si esegue una libreria utilizzando un semplice MySQL "libri" tabella con tre colonne:Accelerare conteggio fila in MySQL

(id, titolo, stato)

  • id è la chiave primaria
  • titolo è il titolo del libro
  • stato potrebbe essere un enum che descrive la b lo stato attuale di ook (ad es. DISPONIBILI, CheckedOut, TRASFORMAZIONE, MISSING)

una semplice query di riferire quanti libri cadere in ogni stato è:

SELECT status, COUNT(*) FROM books GROUP BY status 

o per trovare in particolare quanti libri sono disponibili:

SELECT COUNT(*) FROM books WHERE status = "AVAILABLE" 

Tuttavia, una volta che la tabella raggiunge milioni di righe, queste query richiedono diversi secondi. L'aggiunta di un indice alla colonna "stato" non sembra fare la differenza nella mia esperienza.

Oltre a memorizzare periodicamente i risultati nella cache o aggiornare in modo esplicito le informazioni di riepilogo in una tabella separata ogni volta che un libro cambia stato (tramite trigger o qualche altro meccanismo), esistono tecniche per accelerare questo tipo di query? Sembra che le query COUNT finiscano per esaminare ogni riga e (senza conoscere ulteriori dettagli) sono un po 'sorpreso dal fatto che questa informazione non possa in qualche modo essere determinata dall'indice.

UPDATE

Utilizzando la tabella di esempio (con una colonna indicizzata "status") con 2 milioni di righe, ho benchmark la query GROUP BY. Usando il motore di archiviazione InnoDB, la query impiega 3,0 - 3,2 secondi sulla mia macchina. Usando MyISAM, la query richiede da 0,9 a 1,1 secondi. In entrambi i casi non è stata riscontrata alcuna differenza significativa tra conteggio (*), conteggio (stato) o conteggio (1).

MyISAM è certamente un po 'più veloce, ma ero curioso di vedere se ci fosse un modo per fare una corsa di query equivalente molto più veloce (ad esempio 10-50 ms - abbastanza veloce per essere chiamati a ogni richiesta pagina web per un sito a basso traffico) senza il sovraccarico mentale di caching e trigger. Sembra che la risposta sia "non c'è modo di eseguire rapidamente la query diretta", che è quello che mi aspettavo - volevo solo assicurarmi che non mi mancasse un'alternativa facile.

+1

Fa la differenza quando si utilizza: selezionare il conteggio (colonna_indicizzato) dal libro? –

+0

stai usando innodb o myisam? –

+0

@Boekwurm: Non :). mysql ottimizza la query in modo che count (indexedcolumn), count (*) e count (1) ritornino con lo stesso livello di efficienza. – Alterlife

risposta

36

Quindi la domanda è

Ci sono delle tecniche per accelerare questo tipo di domande?

Beh, non proprio. Un motore di archiviazione basato su colonne sarebbe probabilmente più veloce con quelle SELECT COUNT (*), ma sarebbe meno performante per quasi tutte le altre query.

La soluzione migliore è mantenere una tabella di riepilogo tramite trigger. Non ha molto overhead e la parte SELECT sarà istantanea, non importa quanto grande sia il tavolo. Ecco alcuni codice standard:

DELIMITER // 

CREATE TRIGGER ai_books AFTER INSERT ON books 
FOR EACH ROW UPDATE books_cnt SET total = total + 1 WHERE status = NEW.status 
// 
CREATE TRIGGER ad_books AFTER DELETE ON books 
FOR EACH ROW UPDATE books_cnt SET total = total - 1 WHERE status = OLD.status; 
// 
CREATE TRIGGER au_books AFTER UPDATE ON books 
FOR EACH ROW 
BEGIN 
    IF (OLD.status <> NEW.status) 
    THEN 
     UPDATE books_cnt SET total = total + IF(status = NEW.status, 1, -1) WHERE status IN (OLD.status, NEW.status); 
    END IF; 
END 
// 
+1

Una domanda, che ne dici di viste anziché di trigger? Una vista sarebbe più veloce rispetto a eseguire la query sulla tabella originale? – Stewie

+0

No, finché MySQL non implementa viste materializzate, le loro prestazioni saranno più o meno le stesse dell'istruzione SELECT corrispondente. –

+0

Ma questo non vola di fronte alla logica SQL interna? MySQL non mantiene una riga STIMA perché è IMPOSSIBILE mantenere un conteggio esatto di riga senza significativi problemi di prestazioni? Ad esempio, in molte istanze si verifica il blocco del livello di cella o di riga.Il che significa che è possibile inserire/eliminare due righe contemporaneamente, ma non se la si implementa poiché tutto è legato a un singolo dato, che può essere modificato solo uno alla volta. – Jonathon

8

da: http://dev.mysql.com/doc/refman/5.0/en/innodb-restrictions.html

InnoDB non mantiene un conteggio interno di righe in una tabella. (In pratica, questo sarebbe un po 'complicato a causa della multi-versione .) Per elaborare un SELECT COUNT (*) FROM t, InnoDB deve scansionare un indice della tabella, che richiede un po' di tempo se l'indice è non interamente nel pool di buffer.

La soluzione suggerita è:

Per ottenere un conteggio veloce, è necessario utilizzare una tabella contatore creato da voi e lasciate che la vostra applicazione aggiornarlo secondo le inserti ed elimina esso lo fa. SHOW TABLE STATUS può anche essere utilizzato se un conteggio approssimativo delle righe è sufficiente.

In breve: count (*) (su innoDB) richiederà molto tempo per le tabelle contenenti un numero elevato di righe. Questo è di progettazione e non può essere aiutato.

Scrivi la tua soluzione.

+6

Il brano che hai citato NON si applica al caso in esame. MyISAM ottimizza solo COUNT (*) senza alcuna clausola WHERE, che non è il caso qui. –

9

MyISAM è in realtà piuttosto veloce con count (*) il rovescio della medaglia è che la memorizzazione MyISAM non è così affidabile e meglio evitare in cui l'integrità dei dati è fondamentale.

InnoDB può essere molto lenta per eseguire il conteggio (*) tipo query, causa è progettato per consentire viste multiple simultanee degli stessi dati. Quindi in qualsiasi momento, non è abbastanza per andare all'indice per ottenere il conteggio.

Da:

Il database inizia con 1000 record in esso comincio una transazione Si avvia una transazione elimino 50 record È aggiungere 50 record faccio un COUNT () e vedere 950 record. Fai un COUNT () e vedi i record 1050 . Commetto la mia transazione - il database ora ha 950 record per tutti tranne te. Commetti la tua transazione - il database ha 1000 record 1000.

Come InnoDB passo con cui registra sono "visibile" o "modificabili" con relazione a qualsiasi transazione viene attraverso blocco a livello di riga, transazione livelli di isolamento, e multi-versioni. http://dev.mysql.com/doc/refman/4.1/en/innodb-transaction-model.html http://dev.mysql.com/doc/refman/4.1/en/innodb-multi-versioning.html

Questo è ciò che rende contare quante registra ogni persona può vedere non è così straight-forward.

linea di fondo è quindi avrete bisogno di guardare la memorizzazione nella cache i conteggi in qualche modo invece di andare al tavolo se avete bisogno di arrivare a queste informazioni spesso e veloce.

1

vi era alcuna differenza significativa tra count (*), count (stato), o count (1)

conteggio

(colonna) restituisce il numero di linee dove colonna NON è NULL. Poiché 1 NON è NULL, e lo stato è anche, presumibilmente, NOT NULL, il database ottimizzerà il test e convertirà tutti in count (*). Che, ironia della sorte, non significa "contare le righe dove tutte le colonne non sono nulle" (o qualsiasi altra combinazione), significa semplicemente "contare le righe" ...

Ora, tornando alla tua domanda, non puoi avere la botte piena e la moglie ubriaca ...

  • Se si desidera una "esatta" contare fino a essere disponibile in ogni momento, poi si deve aumentare e diminuire in tempo reale, tramite trigger, che rallenta i tuoi scrive

  • Oppure è possibile utilizzare il conteggio (*), ma questo sarà lento

  • Oppure puoi accontentarti di una stima approssimativa, o di un valore obsoleto, e usare il caching o altri approcci probabilistici.

In generale, su valori al di sopra di "pochi", nessuno è interessato a un conteggio esatto in tempo reale. È comunque un'aringa rossa, perché quando lo leggerete, molto probabilmente il valore sarà cambiato.

2

Molte risposte qui detto un indice non aiuterebbe, ma nel mio caso lo ha fatto ...

mia tabella utilizzata MyISAM, e aveva solo circa 100k righe. La query:

select count(*) from mytable where foreign_key_id=n 

ha richiesto 7-8 secondi.

ho aggiunto un indice sul foreign_key_id:

create index myindex on mytable (foreign_key_id) using btree; 

Dopo aver creato l'indice, l'istruzione select sopra riportato un tempo di esecuzione di 0.00 secondi.

+1

La seconda query probabilmente ha colpito la cache della query, restituendo immediatamente l'ultimo risultato indipendentemente dall'indicizzazione. – henry700

+3

Buon punto: ho appena riprovato a eseguire la mia query (alcuni giorni dopo e il contenuto della tabella è stato modificato) e il conteggio ha impiegato 0,02 secondi. Quindi probabilmente hai ragione riguardo alla cache, ma sembra comunque che l'indice abbia aiutato in modo significativo. – Witt

+0

aggiungi sql_no_cache dopo aver selezionato per evitare la memorizzazione nella cache – JoTAZUZ