2009-02-25 9 views
42

Sto cercando di ottimizzare alcune delle query del database nella mia app Rails e ne ho diverse che mi hanno bloccato. Stanno tutti utilizzando un IN nella clausola WHERE e stanno facendo tutte le scansioni complete della tabella anche se sembra che sia presente un indice appropriato.MySQL non utilizza gli indici con la clausola WHERE IN?

Ad esempio:

SELECT `user_metrics`.* FROM `user_metrics` WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

esegue una scansione completa della tabella e spiegare dice:

select_type: simple 
type: all 
extra: using where 
possible_keys: index_user_metrics_on_user_id (which is an index on the user_id column) 
key: (none) 
key_length: (none) 
ref: (none) 
rows: 208 

Are indici non utilizzati quando si usa un'istruzione IN o devo fare qualcosa di diverso? Le query qui sono state generate da Rails, così ho potuto rivisitare come sono definite le mie relazioni, ma ho pensato di iniziare con potenziali correzioni a livello di DB.

+0

Che cosa significa la tua N? Sono costanti letterali, colonne o variabili? È importante. – Quassnoi

+0

Siamo spiacenti, quelli sono stati incollati dall'output del mio plugin query_reviewer. Le query effettive contengono numeri interi - es. IN (25, 26, 27) – jasonlong

+0

@blackant: hai eseguito l'analisi sui tuoi tavoli? – vladr

risposta

0

Funziona meglio se si rimuovono le parentesi ridondanti attorno alla clausola where?

Anche se potrebbe essere solo perché hai solo 200 o più righe, ha deciso che una scansione della tabella sarebbe stata più veloce. Prova con una tabella con più record al suo interno.

+0

Gli extra paren non sembrano avere importanza. Inoltre, il set di dati di piccole dimensioni non sembra avere importanza: ho aggiunto altri 5000 record e li analizzo ancora tutti. – jasonlong

7

provare a forzare questo indice:

SELECT `user_metrics`.* 
FROM `user_metrics` FORCE INDEX (index_user_metrics_on_user_id) 
WHERE (`user_metrics`.user_id IN (N,N,N,N,N,N,N,N,N,N,N,N)) 

Ho appena controllato, si fa uso di un indice su esattamente stessa query:

EXPLAIN EXTENDED 
SELECT * FROM tests WHERE (test IN ('test 1', 'test 2', 'test 3', 'test 4', 'test 5', 'test 6', 'test 7', 'test 8', 'test 9')) 

1, 'SIMPLE', 'tests', 'range', 'ix_test', 'ix_test', '602', '', 9, 100.00, 'Using where' 
+2

Particolarmente doloroso da implementare in Rails – vladr

+0

Sì, sembra forzarlo a usare l'indice. Come Vlad dice, un dolore da fare in Rails. – jasonlong

+0

@blackant, hai eseguito l'analisi sui tavoli? ancora ottenendo lo stesso piano di spiegazioni? – vladr

37

Vedi How MySQL Uses Indexes.

Convalidare anche se MySQL esegue ancora un full table scan dopo aver aggiunto altre 2000 righe alla tabella user_metrics. Nelle tabelle piccole, l'accesso per indice è in realtà più costoso (I/O-saggio) di una scansione della tabella e l'ottimizzatore di MySQL potrebbe tenerne conto.

Contrariamente al mio precedente post, si scopre che MySQL è anche using a cost-based optimizer, che è una buona notizia - che è, a condizione che si esegue il ANALYZE almeno una volta quando si crede che il volume dei dati nel database è rappresentante del futuro utilizzo quotidiano.

Quando si utilizzano gli ottimizzatori basati sui costi (Oracle, Postgres, ecc.), È necessario assicurarsi di eseguire periodicamente lo ANALYZE sulle varie tabelle poiché le loro dimensioni aumentano di oltre il 10-15%. (Postgres lo farà automaticamente per te, di default, mentre altri RDBMS lasceranno questa responsabilità a un DBA, cioè tu.) Tramite l'analisi statistica, ANALYZE aiuterà l'ottimizzatore a farsi un'idea migliore di quanto I/O (e altri associati risorse, come la CPU, necessarie ad esempio per l'ordinamento) saranno coinvolte nella scelta tra i vari piani di esecuzione dei candidati. La mancata esecuzione ANALYZE può provocare molto povere, le decisioni di pianificazione a volte disastrose (ad es millisecondo-query di prendere, a volte, ore a causa di cattivi cicli annidati su JOIN s.)

Se le prestazioni sono ancora insoddisfacente dopo l'esecuzione ANALYZE, poi in genere sarai in grado di risolvere il problema utilizzando i suggerimenti, ad es FORCE INDEX, mentre in altri casi potresti esserti imbattuto in un bug di MySQL (ad esempio questo older one, che avrebbe potuto essere morso dovresti utilizzare Rails 'nested_set).

Ora, poiché ci si trova in un'applicazione Rails, sarà ingombrante (e sconfiggere lo scopo di ActiveRecord) a rilasciare le vostre query personalizzate con note invece di continuare a utilizzare i ActiveRecord quelli -Generata.

avevo detto che in applicazione nostri Rails tuttiSELECT query è sceso al di sotto di 100 ms dopo il passaggio a Postgres, mentre alcuni del complesso join generati da ActiveRecord ogni tanto prendere quanto 15s o più con MySQL 5.1 a causa dei cicli annidati con le scansioni del tavolo interno, anche quando gli indici erano disponibili. Nessun ottimizzatore è perfetto e devi essere consapevole delle opzioni. Altri potenziali problemi di prestazioni da tenere in considerazione, oltre all'ottimizzazione del piano di query, sono bloccanti. Questo è al di fuori della portata del tuo problema però.

+0

Grazie Vlad . Spero di risolvere questo problema senza troppi problemi con la nostra configurazione attuale, ma apprezzo molto sentir parlare del tuo successo con Postgres. – jasonlong

+0

I secondo Postgres. È un database eccellente. –

+0

Ciao, nero, hai fatto qualche progresso con questo problema MySQL? – vladr

6

A volte MySQL non utilizza un indice, anche se disponibile. Una circostanza in cui ciò si verifica è quando l'ottimizzatore stima che l'utilizzo dell'indice richiederebbe a MySQL di accedere a una percentuale molto ampia delle righe nella tabella. (In questo caso, è probabile che una scansione della tabella sia molto più veloce perché richiede meno ricerche.)

Quale percentuale di righe corrisponde alla clausola IN?

+0

I miei test iniziali erano su una tabella con solo ~ 200 righe, quindi la percentuale è relativamente alta. Tuttavia, ho aggiunto 5000 nuove righe da testare e continua a eseguire scansioni complete della tabella. La percentuale ora sarebbe abbastanza minuscola. – jasonlong

+0

In base alla mia esperienza, il limite è _usualmente_ tra il 10% e il 30%. –

3

So che sono in ritardo per la festa. Ma spero di poter aiutare qualcun altro con problemi simili.

Ultimamente, sto avendo lo stesso problema. Poi decido di usare self-join-thing per risolvere il mio problema. Il problema non è MySQL. Il problema siamo noi Il tipo di ritorno dalla sottoquery è la differenza dalla nostra tabella. Quindi dobbiamo eseguire il cast del tipo di subquery sul tipo di colonna select. Di seguito è riportato codice di esempio:

select `user_metrics`.* 
from `user_metrics` um 
join (select `user_metrics`.`user_id` in (N, N, N, N)) as temp 
on um.`user_id` = temp.`user_id` 

O mio codice:

Vecchio: (Non utilizzare index: ~ 4s)

SELECT 
    `jxm_character`.* 
FROM 
    jxm_character 
WHERE 
    information_date IN (SELECT DISTINCT 
      (information_date) 
     FROM 
      jxm_character 
     WHERE 
      information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) 
     AND `jxm_character`.`ranking_type` = 1 
     AND `jxm_character`.`character_id` = 3146089; 

Nuovo: (Usa indice: ~ 0.02s)

SELECT 
    * 
FROM 
    jxm_character jc 
     JOIN 
    (SELECT DISTINCT 
     (information_date) 
    FROM 
     jxm_character 
    WHERE 
     information_date >= DATE_SUB('2016-12-2', INTERVAL 7 DAY)) AS temp 
     ON jc.information_date = STR_TO_DATE(temp.information_date, '%Y-%m-%d') 
     AND jc.ranking_type = 1 
     AND jc.character_id = 3146089; 

jxm_character:

  • Records: ~ 3.5M
  • PK: jxm_character (information_date, ranking_type, character_id)

SHOW VARIABLES LIKE '%version%';

'protocol_version', '10' 
'version', '5.1.69-log' 
'version_comment', 'Source distribution' 

Ultima nota: Assicurati di capire indice MySQL più a sinistra regola.

P/s: Ci scusiamo per il mio pessimo inglese. Inserisco il mio codice (produzione, ovviamente) per cancellare la mia soluzione: D.

+0

'IN (SELECT ...)' è noto per essere scarsamente ottimizzato. E hai fatto la "cosa giusta" per trasformarlo in un "JOIN". –