2009-06-23 9 views
22

Come velocizzare select count(*) con group by?
È troppo lento e viene utilizzato molto frequentemente.
Ho un grosso problema con select count(*) e group by con una tabella con più di 3.000.000 di righe.Come velocizzare "select count (*)" con "group by" e "where"?

select object_title,count(*) as hot_num 
from relations 
where relation_title='XXXX' 
group by object_title 

relation_title, object_title è varchar. dove relation_title = 'XXXX', che restituisce più di 1.000.000 di righe, porta agli indici su object_title potrebbe non funzionare correttamente.

+0

Potresti fornire ulteriori dettagli, ad es. l'intera selezione e la struttura della tabella? Un altro primo colpo: stai usando gli indici? – Kosi2801

+0

Di seguito ho aggiunto alcune soluzioni potenziali, ma concordo con Kosi sul fatto che vedere la definizione della tabella (in particolare la lunghezza delle colonne varchar!) E le definizioni dell'indice sarebbero molto utili per diagnosticare ciò. –

+0

Le relazioni sono una tabella Innodb o MyISAM? –

risposta

47

Qui ci sono diverse cose che mi piacerebbe provare, in ordine di difficoltà crescente:

(più facile) - Assicurati di avere il diritto di copertura dell'indice

CREATE INDEX ix_temp ON relations (relation_title, object_title); 

Questo dovrebbe massimizzare il rendimento dato lo schema esistente, poiché (a meno che la tua versione dell'ottimizzatore di MySQL sia davvero stupido!) Ridurrà al minimo la quantità di I/O necessaria per soddisfare la tua query (diversamente dall'indice nell'ordine inverso dove tutto indice deve essere scansionato) e coprirà la query in modo da non dover toccare l'indice cluster.

(un po 'più difficile) - assicurarsi che il proprio campi varchar sono il più piccolo possibile

Una delle sfide Potenza con gli indici varchar su MySQL è che, durante l'elaborazione di una query, il full size dichiarato della il campo verrà inserito nella RAM. Quindi se hai un varchar (256) ma stai usando solo 4 caratteri, stai ancora pagando l'utilizzo della RAM a 256 byte mentre la query viene elaborata. Ahia! Quindi, se puoi ridurre facilmente i tuoi limiti varchar, questo dovrebbe velocizzare le tue domande.

(più difficile) - Normalizzare

il 30% dei tuoi file con un singolo valore stringa è un grido chiaro per la normalizzazione in un'altra tabella in modo che non stai duplicando stringhe milioni di volte. Considera la normalizzazione in tre tabelle e l'utilizzo di ID interi per unirle.

In alcuni casi, è possibile normalizzare sotto le copertine e nascondere la normalizzazione con viste che corrispondono al nome della tabella corrente ... quindi è necessario rendere le query INSERT/UPDATE/DELETE consapevoli della normalizzazione, ma è possibile lascia i tuoi SELECT da solo.

(più difficile) - Hash le colonne di stringa e l'indice gli hash

Se normalizzare mezzi che cambiano troppo codice, ma è possibile modificare lo schema un po ', si può prendere in considerazione la creazione di hash a 128 bit per le colonne delle stringhe (utilizzando MD5 function). In questo caso (a differenza della normalizzazione) non è necessario modificare tutte le query, solo gli INSERT e alcuni SELECT. Ad ogni modo, ti consigliamo di hash i tuoi campi di stringa e quindi di creare un indice sugli hash, ad es.

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash); 

Si noti che avrete bisogno di giocare con il SELECT per assicurarsi che si sta facendo il calcolo tramite l'indice hash e non tirando in indice cluster (necessario per risolvere il valore di testo effettivo della object_title al fine per soddisfare la richiesta).

Inoltre, se relation_title ha una piccola dimensione varchar ma il titolo dell'oggetto ha una dimensione lunga, è possibile potenzialmente eseguire l'hash solo object_title e creare l'indice su (relation_title, object_title_hash).

Si noti che questa soluzione è utile solo se uno o entrambi questi campi sono molto lunghi rispetto alla dimensione degli hash.

Si noti inoltre che vi sono interessanti impatti sulla distinzione tra maiuscole e minuscole e casi di collazione, poiché l'hash di una stringa minuscola non è uguale a un hash di uno maiuscolo. Quindi dovrai assicurarti di applicare la canonicalizzazione alle stringhe prima di eseguirne l'hashing - in altre parole, solo hash in minuscolo se sei in un DB senza distinzione tra maiuscole e minuscole. Potresti anche voler tagliare gli spazi dall'inizio o dalla fine, a seconda di come il tuo DB gestisce gli spazi iniziali/finali.

+0

L'indice di copertura che Justin menziona qui è assolutamente il modo migliore per ottenere buone prestazioni da questa query. – BradC

+0

Grazie, molto utile – mOna

+0

Un campo CHAR è una lunghezza fissa e VARCHAR è un campo di lunghezza variabile. Ciò significa che i requisiti di archiviazione sono diversi: un CHAR richiede sempre la stessa quantità di spazio indipendentemente da ciò che si archivia, mentre i requisiti di archiviazione per un VARCHAR variano in base alla stringa specifica memorizzata. Quindi, rendere il campo Varchar il più piccolo possibile non darebbe molto impatto sulle prestazioni. – NPE

0

c'è un punto in cui è veramente necessario più RAM/CPU/IO. Potresti averlo colpito per il tuo hardware.

Noterò che di solito non è efficace utilizzare gli indici (a meno che non siano di copertura) per le query che colpiscono più dell'1-2% delle righe totali in una tabella. Se la query di grandi dimensioni esegue ricerche di indici e ricerche di segnalibri, potrebbe essere a causa di un piano memorizzato nella cache solo da una query di un giorno totale. Prova ad aggiungere in WITH (INDICE = 0) per forzare una scansione della tabella e vedere se è più veloce.

prendere questo da: http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=4631bab4-0104-47aa-b548-e8428073b6e6&cat=&lang=&cr=&sloc=&p=1

+0

Ho pensato che fosse MS SQL per iniziare, ma il poster ha aggiunto il tag mysql ... –

+0

Si noti che la domanda è contrassegnata "mysql" non "mssql". – Kosi2801

+0

sì, 'mysql'. Ho provato "force index (primary)" per avere mysql che non usa index da solo. È efficace, da 20 a 15 anni. –

0

Se ciò che la dimensione di tutto il tavolo, si dovrebbe interrogare le tabelle meta o informazioni dello schema (che esistono su ogni DBMS lo so, ma io non sono sicuro di MySQL). Se la tua query è selettiva, devi assicurarti che ci sia un indice per questo.

AFAIK non c'è nient'altro che puoi fare.

10

L'indicizzazione delle colonne nella clausola GROUP BY sarebbe la prima cosa da provare, utilizzando un indice composito. A una query come questa può essere data risposta utilizzando solo i dati di indice, evitando la necessità di eseguire la scansione del tavolo. Poiché i record nell'indice sono ordinati, il DBMS non deve necessariamente eseguire un ordinamento separato come parte dell'elaborazione di gruppo. Tuttavia, l'indice rallenterà gli aggiornamenti della tabella, quindi sii cauto con questo se la tua tabella subisce pesanti aggiornamenti.

Se si utilizza InnoDB per la memorizzazione della tabella, le righe della tabella verranno fisicamente raggruppate in base all'indice della chiave primaria. Se ciò (o una parte importante di esso) dovesse corrispondere al tuo tasto GROUP BY, dovrebbe accelerare una query come questa perché i record correlati verranno recuperati insieme. Di nuovo, questo evita di dover eseguire un ordinamento separato.

In generale, gli indici di bitmap sarebbero un'altra alternativa efficace, ma MySQL attualmente non li supporta, per quanto ne so.

Una vista materializzata sarebbe un altro approccio possibile, ma ancora una volta questo non è supportato direttamente in MySQL. Tuttavia, se non hai richiesto che le statistiche COUNT fossero completamente aggiornate, puoi periodicamente eseguire un'istruzione CREATE TABLE ... AS SELECT ... per memorizzare manualmente i risultati. Questo è un po 'brutto in quanto non è trasparente, ma potrebbe essere accettabile nel tuo caso.

È inoltre possibile mantenere una tabella di cache a livello logico utilizzando i trigger. Questa tabella avrebbe una colonna per ogni colonna nella clausola GROUP BY, con una colonna Count per l'archiviazione del numero di righe per quel particolare valore della chiave di raggruppamento.Ogni volta che una riga viene aggiunta o aggiornata nella tabella di base, inserire o incrementare/decrementare la riga del contatore nella tabella di riepilogo per quella particolare chiave di raggruppamento. Questo potrebbe essere migliore del falso approccio alla visualizzazione materializzata, poiché il riepilogo memorizzato nella cache sarà sempre aggiornato e ogni aggiornamento verrà eseguito in modo incrementale e dovrebbe avere un impatto minore sulle risorse. Penso che dovresti stare attenti alla contesa del blocco sul tavolo della cache, comunque.

+1

Piccole colonne possono aiutare: se la scansione della tabella è inevitabile, una tabella più piccola richiederà meno tempo per la scansione. Forse potresti pubblicare la struttura della tabella e alcuni dati di esempio insieme alla query esatta. – cheduardo

6

Se si dispone di InnoDB, il conteggio (*) e qualsiasi altra funzione di aggregazione eseguiranno una scansione della tabella. Vedo alcune soluzioni qui:

  1. Utilizzare i trigger e memorizzare gli aggregati in una tabella separata. Pro: integrità. Contro: aggiornamenti lenti
  2. Utilizzare code di elaborazione. Pro: aggiornamenti veloci. Contro: il vecchio stato può persistere fino a quando la coda non viene elaborata in modo che l'utente possa sentirsi privo di integrità.
  3. Separare completamente il livello di accesso alla memoria e archiviare gli aggregati in una tabella separata. Il livello di archiviazione sarà a conoscenza della struttura dei dati e può applicare delta anziché eseguire conteggi completi. Ad esempio, se fornisci una funzionalità "addObject" all'interno di questa, saprai quando un oggetto è stato aggiunto e quindi l'aggregato sarebbe interessato. Quindi fai solo un update table set count = count + 1. Pro: aggiornamenti rapidi, integrità (potresti voler utilizzare un blocco anche se diversi client possono modificare lo stesso record). Contro: accoppi un po 'di logica e archiviazione aziendale.
+0

+1, Iive gota prova questo concetto ... Ho problemi simili –

1

prova count (myprimaryindexcolumn) e confrontare le prestazioni per il valore (*)

2

Vedo che alcune persone hanno chiesto quale motore si stava utilizzando per la query. Mi raccomando di utilizzare MyISAM per le seguenti reasions:

InnoDB - @Sorin Mocanu correttamente identificato che si farà un tavolo pieno di scansione indipendentemente indici.

MyISAM - mantiene sempre utile il conteggio delle righe corrente.

Infine, per quanto @justin detto, assicuratevi di avere il corretto indice di copertura:

CREATE INDEX ix_temp ON relations (relation_title, object_title); 
+4

FYI, l'enorme vantaggio in termini di velocità delle query MyISAM per COUNT (*) si applica solo quando si contano le righe nell'intera tabella. Se c'è una clausola WHERE, allora sia MyISAM che InnoDB calcolano il conteggio contando le righe nell'indice. Vedi http://www.mysqlperformanceblog.com/2006/12/01/count-for-innodb-tables/ per maggiori informazioni. –

0

vorrei suggerire di archiviare i dati a meno che non v'è alcun motivo specifico per tenerlo nel database o si potrebbe suddividere il dati ed eseguire query separatamente.