Domanda

Come velocizzare selezionare il conteggio (*) con raggruppare per ?
È troppo lento e viene usato molto frequentemente.
Ho un grosso problema usando select count (*) e raggruppa per con una tabella con più di 3.000.000 di righe.

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

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

È stato utile?

Soluzione

Ecco alcune cose che proverei, in ordine di difficoltà crescente:

(più semplice) - Assicurati di avere l'indice di copertura giusto

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Questo dovrebbe massimizzare perf dato il tuo schema esistente, poiché (a meno che la tua versione dell'ottimizzatore di mySQL sia davvero stupida!) ridurrà al minimo la quantità di I / O necessari per soddisfare la tua richiesta (diversamente dal caso in cui l'indice sia nell'ordine inverso dove l'intero indice deve essere scansionato) e coprirà la query in modo da non dover toccare l'indice cluster.

(un po 'più difficile): assicurati che i tuoi campi varchar siano i più piccoli possibili

Una delle sfide perf con gli indici varchar su MySQL è che, durante l'elaborazione di una query, l'intera dimensione dichiarata del campo verrà trasferita nella RAM. Quindi se hai un varchar (256) ma stai usando solo 4 caratteri, stai ancora pagando l'utilizzo della RAM a 256 byte durante l'elaborazione della query. Ahia! Quindi, se puoi ridurre facilmente i tuoi limiti varchar, questo dovrebbe accelerare le tue domande.

(più difficile): normalizza

Il 30% delle tue righe con un singolo valore di stringa è un chiaro grido per la normalizzazione in un'altra tabella, quindi non duplicherai stringhe milioni di volte. Prendi in considerazione la normalizzazione in tre tabelle e l'utilizzo di ID interi per unirle.

In alcuni casi, puoi normalizzare sotto le copertine e nascondere la normalizzazione con viste che corrispondono al nome della tabella corrente ... quindi devi solo rendere le tue richieste INSERT / UPDATE / DELETE consapevoli della normalizzazione ma puoi lasciare solo i tuoi SELEZIONATI.

(più difficile) - Hash le colonne di stringa e indicizza gli hash

Se normalizzare significa modificare troppo codice, ma è possibile modificare leggermente lo schema, è consigliabile prendere in considerazione la creazione di hash a 128 bit per le colonne della stringa (utilizzando funzione MD5 ). In questo caso (diversamente dalla normalizzazione) non è necessario modificare tutte le query, solo gli INSERTI e alcuni dei SELEZIONATI. Ad ogni modo, ti consigliamo di eseguire l'hashing dei campi della stringa e quindi creare un indice sugli hash, ad es.

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);

Nota che dovrai giocare con SELECT per assicurarti di fare il calcolo tramite l'indice hash e di non inserire l'indice cluster (necessario per risolvere il valore di testo effettivo di object_title per soddisfare la query ).

Inoltre, se relationship_title ha una dimensione varchar piccola ma il titolo dell'oggetto ha una dimensione lunga, allora puoi potenzialmente eseguire l'hashing solo object_title e creare l'indice su (relationship_title, object_title_hash) .

Nota che questa soluzione aiuta solo se uno o entrambi questi campi sono molto lunghi rispetto alla dimensione degli hash.

Nota anche che ci sono interessanti effetti di maiuscole / minuscole derivanti dall'hash, dato che l'hash di una stringa in minuscolo non è lo stesso di un hash di una maiuscola. Quindi dovrai assicurarti di applicare la canonicalizzazione alle stringhe prima di eseguire l'hashing di esse - in altre parole, usa solo lettere minuscole se sei in un DB senza distinzione tra maiuscole e minuscole. Potresti anche voler tagliare gli spazi dall'inizio o alla fine, a seconda di come il tuo DB gestisce gli spazi iniziali / finali.

Altri suggerimenti

Indicizzare le colonne nella clausola GROUP BY sarebbe la prima cosa da provare, usando un indice composito. È possibile rispondere a una query come questa utilizzando solo i dati dell'indice, evitando la necessità di eseguire la scansione della tabella. Poiché i record nell'indice sono ordinati, il DBMS non dovrebbe aver bisogno di eseguire un ordinamento separato come parte dell'elaborazione del gruppo. Tuttavia, l'indice rallenterà gli aggiornamenti della tabella, quindi sii cauto con questo se la tua tabella subisce aggiornamenti pesanti.

Se si utilizza InnoDB per l'archiviazione della tabella, le righe della tabella verranno raggruppate fisicamente in base all'indice della chiave primaria. Se tale (o una parte iniziale di esso) corrisponde alla chiave GROUP BY, ciò dovrebbe accelerare una query come questa perché i record correlati verranno recuperati insieme. Ancora una volta, questo evita di dover eseguire un ordinamento separato.

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

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

Puoi anche mantenere una tabella cache a livello logico usando i trigger. Questa tabella avrebbe una colonna per ogni colonna nella clausola GROUP BY, con una colonna Count per memorizzare il numero di righe per quel particolare valore di 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 dell'approccio basato sulla falsa 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 fare attenzione alla contesa di blocco sulla tabella della cache, tuttavia.

Se si dispone di InnoDB, count (*) e qualsiasi altra funzione aggregata eseguirà una scansione della tabella. Vedo alcune soluzioni qui:

  1. Utilizza i trigger e archivia gli aggregati in una tabella separata. Pro: integrità. Contro: aggiornamenti lenti
  2. Utilizza le code di elaborazione. Pro: aggiornamenti rapidi. Contro: il vecchio stato può persistere fino a quando la coda non viene elaborata, quindi l'utente può avvertire una mancanza di integrità.
  3. Separare completamente il livello di accesso all'archiviazione e archiviare gli aggregati in una tabella separata. Il livello di archiviazione sarà a conoscenza della struttura dei dati e può applicare delta invece di eseguire conteggi completi. Ad esempio, se fornisci un " addObject " funzionalità all'interno che saprai quando è stato aggiunto un oggetto e quindi l'aggregato ne risentirebbe. Quindi esegui solo un set di tabelle di aggiornamento count = count + 1 . Pro: aggiornamenti rapidi, integrità (potresti voler utilizzare un blocco nel caso in cui più client possano modificare lo stesso record). Contro: abbini un po 'di logica aziendale e archiviazione.

Vedo che alcune persone hanno chiesto quale motore stavi utilizzando per la query. Consiglio vivamente di utilizzare MyISAM per le seguenti affermazioni:

InnoDB - @Sorin Mocanu ha identificato correttamente che eseguirai una scansione completa della tabella indipendentemente dagli indici.

MyISAM : consente di tenere sempre a portata di mano il conteggio delle righe correnti.

Infine, come affermato da @justin, assicurati di avere l'indice di copertura adeguato:

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Test  count (myprimaryindexcolumn) e confronta il rendimento con il tuo conteggio (*)

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

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

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

Se hai le dimensioni dell'intera tabella, dovresti interrogare le meta tabelle o lo schema informativo (che esistono su ogni DBMS che conosco, ma non sono sicuro di MySQL). Se la tua query è selettiva, devi assicurarti che ci sia un indice per essa.

AFAIK non c'è altro da fare.

Suggerirei di archiviare i dati a meno che non vi siano motivi specifici per conservarli nel database o non sia possibile partizionare i dati ed eseguire le query separatamente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top