2012-03-01 19 views
9

Ho una query che mi sta dando problemi e non riesco a capire perché il Query Optimizer di MySQL si comporti così com'è. Ecco le informazioni di base:Perché MySQL non può ottimizzare questa query?

Ho 3 tavoli. Due sono relativamente piccoli e uno è grande.

Tabella 1 (molto piccole, 727 righe):

CREATE TABLE ipa (
ipa_id int (11) AUTO_INCREMENT NOT NULL,
ipa_code int (11) NULL DEFAULT
ipa_name varchar (100) NULL DEFAULT
payorcode varchar (2) NULL DEFAULT
compid int (11) DEFAULT '2'
PRIMARY KEY (ipa_id),
PRINCIPALI ipa_code (ipa_code)) MOTORE = MyISAM

Tabella 2 (piccole, 59455 righe):

CREATE TABLE assign_ipa (
assignid int (11) NOT NULL AUTO_INCREMENT,
ipa_id int (11) NOT NULL,
userid int (11) NON NULLO L,
username varchar (20) PREDEFINITO NULL,
compid int (11) PREDEFINITO NULL,
PayorCode char (10) PREDEFINITO NULL
chiave primaria (assignid),
UNIQUE KEY assignid (assignid, ipa_id),
KEY ipa_id (ipa_id)
) MOTORE = MyISAM

Tabella 3 (grandi, 24,711,730 righe):

CREATE TABLE master_final (
IPA int (11) PREDEFINITO NULL,
MbrCt smallint (6) DEFAULT '0',
PayorCode varchar (4) STANDARD 'WC',
KEY idx_IPA (IPA)
) ENGINE = MyISAM DI DEFAULT

Ora per la query. Sto facendo un join a 3 vie usando le prime due tabelle più piccole per sommare essenzialmente la grande tabella su uno dei suoi valori indicizzati. Fondamentalmente, ottengo una lista di ID per un utente, SJOnes e interrogare il grande file per quegli ID.

mysql> spiegare
SELEZIONA master_final.PayorCode, sum (master_final.Mbrct) AS MbrCt
DA master_final
INNER JOIN IPA ON ipa.ipa_code = master_final.IPA
INNER JOIN assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id
DOVE assign_ipa.username = 'SJones'
GROUP BY master_final.PayorCode, master_final.ipa \ G;
* ** * ** * ** * ** * 1. fila * ** * ** * ** * * * *
id: 1
select_type: SEMPLICE
tavolo: master_final
Tipo: TUTTI
possible_keys: idx_IPA
chiave: NULL
key_len: NULL
ref: NULL
righe:
Extra: Utilizzo temporaneo; Utilizzando filesort
* ** * ** * ** * ** * 2. riga * ** * ** * ** * ** *
id: 1
select_type: SEMPLICE 01.238.007,316 mila tabella: IPA
Tipo: rif
possible_keys: PRIMARIO, ipa_code
chiave: ipa_code
key_len: 5
rif: wc_test.master_final.IPA
righe: 1
Extra: Usando dove
* *** *** *** *** 3.fila * ** * ** * ** * ** *
id: 1
select_type: SEMPLICE
tabella: assign_ipa
Tipo: ref
possible_keys: ipa_id
chiave: ipa_id
key_len: 4
rif: wc_test.ipa .ipa_id
righe: 37
Extra: (! come 30 minuti) con cui
3 rows in set (0.00 sec)

Questa query richiede sempre. La spiegazione spiega perché, sta facendo una scansione completa della tabella sul grande tavolo anche se c'è un indice perfettamente buono. Non lo sta usando. Non lo capisco Posso esaminare la query e vedere che è solo necessario interrogare un paio di ID dal grande tavolo. Se posso farlo, perché l'ottimizzatore di MySQL non può farlo?

Per illustrare, qui sono gli ID associati 'SJones':

mysql> select nome utente, ipa_id da assign_ipa dove username = 'SJones';
+ ---------- + -------- +
| nome utente | ipa_id |
+ ---------- + -------- +
| SJones | 688 |
| SJones | 689 |
+ ---------- + -------- +
2 rows in set (0.02 sec)

Ora, posso riscrivere la query sostituendo i valori ipa_id per il nome utente nella clausola where. Per me questo è equivalente alla query originale. MySQL lo vede diversamente. Se faccio questo, l'ottimizzatore fa uso dell'indice sul grande tavolo.

mysql> spiegare
SELEZIONA master_final.PayorCode, sum (master_final.Mbrct) AS MbrCt
DA master_final
INNER JOIN IPA ON ipa.ipa_code = master_final.IPA
INNER JOIN assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id
* WHERE assign_ipa.ipa_id in ('688', '689') *
GROUP BY master_final.PayorCode, master_final.ipa \ G;
* ** * *** *** *** 1.fila * *** *** *** ***
id: 1
select_type: SEMPLICE
tabella: IPA
Tipo: gamma
possible_keys: PRIMARY, ipa_code
chiave: PRIMARY
key_len: 4
ref: NULL
righe: 2
Extra: Utilizzo di dove; Uso temporaneo; Utilizzando filesort
* ** * ** * ** * ** * 2. riga * ** * ** * ** * ** *
id: 1
select_type: SEMPLICE 01.238.007,316 mila tabella: assign_ipa
Tipo: rif
possible_keys: ipa_id
chiave: ipa_id
key_len: 4
ref: wc_test.ipa.ipa_id
righe: 37
Extra: Usando dove
* ** * ** * ** * ** * 3. riga * *** *** *** ***
id: 1
select_type: SEMPLICE
tabella: master_final
Tipo: ref
possibili_keys: idx_IPA
chiave: idx_IPA
key_len: 5
ref: wc_test.ipa.ipa_code
righe:
Extra: Usando dove
3 rows in set (0.00 sec)

L'unica cosa che ho cambiato è una clausola in cui tale non colpisce nemmeno direttamente il grande tavolo. Eppure, l'ottimizzatore utilizza l'indice 'idx_IPA' sulla grande tabella e la scansione completa della tabella non viene più utilizzata. La query quando riscritta in questo modo è molto veloce.

OK, è molto di fondo. Ora la mia domanda. Perché la clausola where dovrebbe avere importanza per l'ottimizzatore? In entrambi i casi la clausola restituirà lo stesso set di risultati dalla tabella più piccola, eppure ottengo risultati drammaticamente diversi a seconda di quale uso. Ovviamente, voglio usare la clausola where che contiene il nome utente piuttosto che provare a passare tutti gli ID associati alla query. Come scritto però, questo non è possibile?

  1. Qualcuno può spiegare perché questo sta accadendo?
  2. Come posso riscrivere la mia query per evitare la scansione completa della tabella?

Grazie per avermi seguito. So che è una domanda molto lunga.

+0

Ho letto un articolo di uno degli sviluppatori MySQL (qualche tempo fa) che l'ottimizzatore era ancora in lavorazione - e quindi sono stati assorbiti da Oracle. Hai provato a utilizzare "suggerimenti" o forse spostando "assign_ipa.username = 'SJones'" nel JOIN? –

risposta

4

Non sono sicuro se ho ragione, ma penso che qui stia succedendo. Questo:

WHERE assign_ipa.username = 'SJones' 

può creare una tabella temporanea, poiché richiede una scansione completa della tabella. Le tabelle temporanee non hanno indici e tendono a rallentare molto le cose.

Il secondo caso

INNER JOIN ipa ON ipa.ipa_code = master_final.IPA 
INNER JOIN assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id 
WHERE assign_ipa.ipa_id in ('688','689') 

dall'altro consente una giunzione di indici, che è veloce. Inoltre, può essere trasformato in

SELECT .... FROM master_final WHERE IDA IN (688, 689) ... 

e penso che anche MySQL lo stia facendo.

La creazione di un indice su assign_ipa.username può aiutare.

Modifica

ho ripensato il problema e ora hanno una spiegazione diversa.

La ragione, naturalmente, è l'indice mancante. Ciò significa che MySQL non ha idea di quanto grande sarebbe il risultato dell'interrogazione su assign_ipa (MySQL non memorizza i conteggi), quindi inizia con i join prima, dove può essere inoltrato sulle chiavi.

Ecco cosa ci dicono le righe 2 e 3 di spiegare il registro.

E dopo che, si cerca di filtrare il risultato per assign_ipa.username, che non ha chiave, come indicato nella riga 1.

appena v'è un indice, filtri assign_ipa prima, e poi si unisce , usando gli indici appropriati.

+0

Ho aggiunto un indice su assign_ipa.username e abbastanza sicuro, che ha risolto il problema. Brillante! Non ci avrei mai pensato. Grazie per l'aiuto! – craigm

+0

+1 ma mi dispiace non averlo capito: perché creare un tavolo temporaneo? Per memorizzare gli ID da assign_ipa rilevati durante la scansione? È davvero fatto in una tabella temporanea per un numero limitato di corrispondenze, ed è qualcosa di cui dovrei preoccuparmi in altri DB (Oracle, SQL)? Grazie! – Rup

+0

Non conosco bene Oracle e MS SQL, ma penso che siano molto migliori nell'utilizzo delle dimensioni della tabella per stimare i costi delle query. –

2

questo probabilmente non è una risposta diretta alla tua domanda, ma qui ci sono alcune cose che si possono fare:

  1. Run ANALYZE_TABLE ... sarà aggiornare le statistiche di tabella che ha un grande impatto su ciò che ottimizzatore deciderà di fare.

  2. Se continui a pensare che i join non siano nell'ordine in cui desideri che siano (cosa che accade nel tuo caso, e quindi l'ottimizzatore non sta usando gli indici come ti aspetti che faccia), puoi usare STRAIGHT_JOIN ... da here: "STRAIGHT_JOIN costringe l'ottimizzatore per unire le tabelle nell'ordine in cui sono elencati nella clausola FROM È possibile utilizzare questo per accelerare una query se l'ottimizzatore unisce le tabelle in ordine non ottimale."

  3. per me , mettere "dove una parte" a destra si unisca a volte fa la differenza e accelera le cose. Ad esempio, si può scrivere:

...t1 INNER JOIN t2 ON t1.k1 = t2.k2 AND t2.k2=something...

invece di

...t1 INNER JOIN t2 ON t1.k1 = t2.k2 .... WHERE t2.k2=something...

Quindi questo non è sicuramente una spiegazione sul motivo per cui si dispone che il comportamento, ma solo alcuni suggerimenti. Query optimizer è una strana bestia, ma fortunatamente c'è il comando EXPLAIN che può aiutarti a ingannarlo per comportarti nel modo che desideri.

Problemi correlati