Come velocizzare le query su una grande tabella da 220 milioni di righe (dati di 9 gig)?


31

Il problema:

Abbiamo un sito social in cui i membri possono valutare l'un l'altro per compatibilità o corrispondenza. Questa user_match_ratingstabella contiene oltre 220 milioni di righe (9 gig data o quasi 20 gig negli indici). Le query su questa tabella vengono visualizzate di routine in slow.log (soglia> 2 secondi) ed è la query lenta registrata più frequentemente nel sistema:

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 1051
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 395357 group by rating;"

Query_time: 4  Lock_time: 0  Rows_sent: 3  Rows_examined: 1294
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 4182969 group by rating;"

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 446
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 630148 group by rating;"

Query_time: 5  Lock_time: 0  Rows_sent: 3  Rows_examined: 3788
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1835698 group by rating;"

Query_time: 17  Lock_time: 0  Rows_sent: 3  Rows_examined: 4311
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1269322 group by rating;"

Versione MySQL:

  • versione protocollo: 10
  • versione: 5.0.77-log
  • versione bdb: Sleepycat Software: Berkeley DB 4.1.24: (29 gennaio 2009)
  • compilazione versione macchina: x86_64 version_compile_os: redhat-linux-gnu

Informazioni sulla tabella:

SHOW COLUMNS FROM user_match_ratings;

dà:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 id             int(11)     NO  PRI  NULL    auto_increment 
 rater_user_id  int(11)     NO  MUL  NULL                   
 rated_user_id  int(11)     NO  MUL  NULL                   
 rating         varchar(1)  NO       NULL                   
 created_at     datetime    NO       NULL                   
╚═══════════════╩════════════╩════╩═════╩════════╩════════════════╝

Query di esempio:

select * from mutual_match_ratings where id=221673540;

dà:

╔═══════════╦═══════════════╦═══════════════╦════════╦══════════════════════╗
 id         rater_user_id  rated_user_id  rating  created_at           
╠═══════════╬═══════════════╬═══════════════╬════════╬══════════════════════╣
 221673540  5699713        3890950        N       2013-04-09 13:00:38  
╚═══════════╩═══════════════╩═══════════════╩════════╩══════════════════════╝

indici

La tabella ha 3 indici impostati:

  1. indice singolo attivo rated_user_id
  2. indice composito su rater_user_idecreated_at
  3. indice composito su rated_user_iderater_user_id
mostra indice da user_match_ratings;

dà:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 Table               Non_unique  Key_name                   Seq_in_index  Column_name    Collation  Cardinality  Sub_part  Packed  Null                     Index_type  Comment          
╠════════════════════╬════════════╬═══════════════════════════╬══════════════╬═══════════════╬═══════════╬═════════════╬══════════╬════════╬═════════════════════════╬════════════╬══════════════════╣
 user_match_ratings  0           PRIMARY                    1             id             A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  1             rater_user_id  A          11039059     NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  2             created_at     A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  1             rated_user_id  A          4014203      NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  2             rater_user_id  A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index3  1             rated_user_id  A          2480687      NULL      NULL    BTREE                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Anche con gli indici queste query sono lente.

La mia domanda:

La separazione di questa tabella / dati in un altro database su un server che ha RAM sufficiente per archiviare questi dati in memoria renderebbe più veloci queste query? C'è qualcosa in qualche modo che le tabelle / gli indici sono impostati su cui possiamo migliorare per rendere più veloci queste query?

Attualmente abbiamo 16 GB di memoria; tuttavia stiamo cercando di aggiornare la macchina esistente a 32 GB o di aggiungere una nuova macchina con almeno quel tanto, forse anche unità a stato solido.


1
La tua domanda è incredibile Sono molto interessato alla tua attuale soluzione che come sei riuscito a ottenere il risultato in <= 2 secondi? Perché ho una tabella che ha solo 20 milioni di record e ci vogliono ancora 30 secondi SELECT QUERY. Potresti suggerire per favore? PS La tua domanda mi ha costretto a unirmi a questa comunità (y);)
NullPointer

2
Guarda gli indici sulla tabella che stai interrogando .. spesso è possibile apportare molti miglioramenti alle query creando l'indice appropriato. Non sempre, ma ho visto molti casi in cui le query vengono eseguite rapidamente fornendo un indice rispetto alle colonne sulla clausola where di una query. Soprattutto se una tabella diventa sempre più grande.
Ranknoodle,

Sicuro @Ranknoodle. Grazie. Controllerò rispettivamente.
NullPointer

Risposte:


28

Pensieri sull'argomento, lanciati in ordine casuale:

  • L'indice ovvia per questa query è: (rated_user_id, rating). Una query che ottiene dati solo per uno dei milioni di utenti e richiede 17 secondi sta facendo qualcosa di sbagliato: leggere (rated_user_id, rater_user_id)dall'indice e quindi leggere dalla tabella i valori (da centinaia a migliaia) per la ratingcolonna, come ratingnon accade in nessun indice. Pertanto, la query deve leggere molte righe della tabella che si trovano in diverse posizioni del disco.

  • Prima di iniziare ad aggiungere numerosi indici nelle tabelle, provare ad analizzare le prestazioni dell'intero database, l'intera serie di query lente, esaminare nuovamente le scelte dei tipi di dati, il motore che si utilizza e le impostazioni di configurazione.

  • Considera di passare a una versione più recente di MySQL, 5.1, 5.5 o anche 5.6 (anche: versioni Percona e MariaDB.) Numerosi vantaggi poiché i bug sono stati corretti, l'ottimizzatore migliorato e puoi impostare la soglia bassa per le query lente su meno di 1 secondo (come 10 millisecondi). Questo ti darà informazioni di gran lunga migliori sulle query lente.

  • La scelta per il tipo di dati di ratingè strana. VARCHAR(1)? Perché no CHAR(1)? Perché no TINYINT? Questo ti farà risparmiare un po 'di spazio, sia nella tabella che negli indici che (includeranno) quella colonna. Una colonna varchar (1) necessita di un ulteriore byte su char (1) e se sono utf8, le colonne (var) char avranno bisogno di 3 (o 4) byte, anziché 1 (tinyint).


2
Quanto impatto sulle prestazioni o spreco di archiviazione in termini di% se si utilizza il tipo di dati errato?
FlyingAtom

1
@FlyingAtom Dipende dal caso, ma per alcune colonne indicizzate che devono ancora essere analizzate (ad esempio quando non si dispone di una clausola where ma si sta recuperando solo quella colonna), il motore potrebbe decidere di eseguire la scansione dell'indice anziché la tabella e se si ottimizza il tipo di dati in una metà della dimensione, la scansione sarebbe due volte più veloce e la risposta sarebbe la metà della dimensione. Se stai ancora eseguendo la scansione della tabella anziché di un indice (ad esempio quando recuperi più colonne non solo quelle nell'indice), i vantaggi sarebbero meno significativi.
Sebastián Grignoli,

-1

Ho gestito tavoli per il governo tedesco con a volte 60 milioni di dischi.

Abbiamo avuto molti di questi tavoli.

E dovevamo conoscere molte volte le righe totali da una tabella.

Dopo aver parlato con i programmatori Oracle e Microsoft non eravamo così felici ...

Quindi noi, il gruppo di programmatori di database, abbiamo deciso che in ogni tabella è registrato uno sempre il record in cui sono memorizzati i numeri di record totali. Abbiamo aggiornato questo numero, a seconda delle righe INSERT o DELETE.

Abbiamo provato tutti gli altri modi. Questo è di gran lunga il modo più veloce.

Usiamo questo modo dal 1998 e non abbiamo mai avuto un numero errato di righe, in tutte le nostre milioni di tabelle di record.


7
Suggerirei di esaminare alcune delle funzionalità introdotte negli ultimi 18 anni. Tra gli altri, count(*)ha alcuni miglioramenti.
dezso

Come fai a sapere che non hai mai avuto un numero sbagliato se non riuscivi a contarli? uhmmmm ...
Tonca,

-3

Proverò a partizionare su tipi di valutazione, come:

mutual_match_ratings_N, mutual_match_ratings_S, ecc.

Dovresti eseguire una query per ogni tipo, ma forse è più veloce dell'altro modo. Provaci.

Ciò presuppone che tu abbia un numero fisso di tipi di valutazione e che non sia necessaria questa tabella per altre query che sarebbero peggiori con questa nuova struttura.

In tal caso, dovresti cercare un altro approccio o mantenere due copie della tabella (la tua tabella iniziale e quelle partizionate) se è conveniente in termini di spazio e manutenibilità (o logica dell'applicazione).

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.