2012-09-26 20 views
12

Il nostro team ha appena trascorso il debug della scorsa settimana e ha cercato di trovare la fonte di molti timeout del blocco mysql e di molte query estremamente lunghe. Alla fine sembra che questa query sia il colpevole.Perché questa query causa il blocco dei timeout di attesa?

mysql> explain 

SELECT categories.name AS cat_name, 
COUNT(distinct items.id) AS category_count 
FROM `items` 
INNER JOIN `categories` ON `categories`.`id` = `items`.`category_id` 
WHERE `items`.`state` IN ('listed', 'reserved') 
    AND (items.category_id IS NOT NULL) 
GROUP BY categories.name 
ORDER BY category_count DESC 
LIMIT 10\G 

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: items 
     type: range 
possible_keys: index_items_on_category_id,index_items_on_state 
      key: index_items_on_category_id 
     key_len: 5 
      ref: NULL 
     rows: 119371 
     Extra: Using where; Using temporary; Using filesort 
*************************** 2. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: categories 
     type: eq_ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: production_db.items.category_id 
     rows: 1 
     Extra: 
2 rows in set (0.00 sec) 

posso vedere che si sta facendo una scansione di tabella brutto e la creazione di una tabella temporanea per l'esecuzione.

Perché questa query causerebbe tempi di risposta del database di un fattore dieci e alcune query che in genere richiedono 40-50 ms (aggiornamenti sulla tabella degli articoli), per esplodere a 50.000 ms o più a volte?

+1

Hai provato profiling * senza * 'distinct'? Ci vuole un bel po 'di lavoro per farlo e si ha un bel po' le righe per filtrare troppo :) – PhD

+0

Very nice. No, non l'ha fatto. Aiuta sicuramente a ottimizzarlo. Non è ancora chiaro perché una query lenta come questa può causare tanti problemi per noi. – chrishomer

+0

chiedo solo perché avete bisogno di questo 'E (items.category_id non è nullo)' - in quanto si tratta di un JOIN' 'INTERNO - è category.id permesso di essere' null' –

risposta

5

è difficile da dire, senza ulteriori informazioni come

  1. è che in esecuzione all'interno di una transazione?
  2. Se sì, qual è il livello di isolamento?
  3. Quante categorie ci sono?
  4. Quanti elementi?

La mia ipotesi è che la query è troppo lento e la sua esecuzione all'interno di una transazione (che probabilmente lo è da quando si dispone di questo problema) ed è probabilmente emittenti range-serrature sulla tabella degli elementi che non possono consentire le scritture per procedere quindi rallentando gli aggiornamenti fino a quando non è possibile ottenere un blocco sul tavolo.

E ho un paio di commenti sulla base di quello che posso vedere dalla query ed esecuzione piano:

1) La tua items.state sarebbe probabilmente meglio come un catalogo, invece di avere la stringa su ogni riga di elementi, questo è per l'efficienza dello spazio e il confronto degli ID è molto più veloce rispetto al confronto delle stringhe (indipendentemente dalle ottimizzazioni che il motore può fare).

2) Sto indovinando items.state è una colonna con cardinalità bassa (alcuni valori univoci), quindi un indice in quella colonna probabilmente ti fa più male che aiutarti. Ogni indice si aggiunge alla testa quando si inseriscono/eliminano/aggiornano le righe dal momento che gli indici devono essere mantenuti, questo particolare indice probabilmente non è usato tanto per cui valga la pena. Certo, sto solo indovinando, dipende dal resto delle domande.

SELECT 
    ; Grouping by name, means comparing strings. 
    categories.name AS cat_name, 
    ; No need for distinct, the same item.id cannot belong to different categories 
    COUNT(distinct items.id) AS category_count 
FROM `items` 
INNER JOIN `categories` ON `categories`.`id` = `items`.`category_id` 
WHERE `items`.`state` IN ('listed', 'reserved') 
    ; Not needed, the inner join gets rid of items with no category_id 
    AND (items.category_id IS NOT NULL) 
GROUP BY categories.name 
ORDER BY category_count DESC 
LIMIT 10\G 

Il modo in cui questa interrogazione è strutturato è sostanzialmente dover scandire l'intera tabella articoli dalla sua utilizzando l'indice category_id, quindi filtrando dalla clausola in cui, poi, unendosi con la tabella delle categorie, che significa un indice di ricerca sulla l'indice della chiave primaria (categories.id) per riga dell'articolo nel set di risultati degli articoli. Quindi raggruppare per nome (usando il confronto delle stringhe) per contare, quindi eliminare tutto tranne 10 dei risultati.

vorrei scrivere la domanda come:

SELECT categories.name, counts.n 
FROM (SELECT category_id, COUNT(id) n 
     FROM items 
     WHERE state IN ('listed', 'reserved') AND category_id is not null 
     GROUP BY category_id ORDER BY COUNT(id) DESC LIMIT 10) counts 
JOIN categories on counts.category_id = categories.id 
ORDER BY counts.n desc   

(mi dispiace se la sintassi non è perfetto non sono in esecuzione MySQL)

Con questa query ciò che il motore sarà probabilmente fare è:

Utilizzare gli elementi.indice di stato per ottenere le voci, 'quotate' 'riservati' e di gruppo da category_id confrontando numeri, non stringhe ottenendo quindi solo il 10 conteggi più in alto, poi unirsi con le categorie per ottenere il nome (ma usando solo il 10 indice cerca).

Problemi correlati