2012-11-15 16 views
9

la seguente query viene eseguita in 1,6 secondiPerché questa clausola WHERE rende la mia query 180 volte più lenta?

SET @num :=0, @current_shop_id := NULL, @current_product_id := NULL; 

#this query limits the results of the query within it by row number (so that only 250 products get displayed per store) 

SELECT * FROM (

#this query adds row numbers to the query within it 

SELECT *, @num := IF(@current_shop_id = shop_id, IF(@current_product_id=product_id,@num,@num+1), 0) AS row_number, @current_shop_id := shop_id AS shop_dummy, @current_product_id := product_id AS product_dummy FROM (

SELECT shop, shops.shop_id AS 
shop_id, p1.product_id AS 
product_id 
    FROM products p1 LEFT JOIN #this LEFT JOIN gets the favorites count for each product 
    (
    SELECT fav3.product_id AS product_id, SUM(CASE 
    WHEN fav3.current = 1 AND fav3.closeted = 1 THEN 1 
    WHEN fav3.current = 1 AND fav3.closeted = 0 THEN -1 
    ELSE 0 
    END) AS favorites_count 
    FROM favorites fav3 
GROUP BY fav3.product_id 

) AS fav4 ON p1.product_id=fav4.product_id 
    INNER JOIN sex ON sex.product_id=p1.product_id AND 
    sex.sex=0 AND 
    sex.date >= SUBDATE(NOW(),INTERVAL 1 DAY) 
    INNER JOIN shops ON shops.shop_id = p1.shop_id 
    ORDER BY shop, sex.DATE, product_id 
    ) AS testtable 

) AS rowed_results WHERE 
rowed_results.row_number>=0 AND 
rowed_results.row_number<(7) 

aggiungendo AND shops.shop_id=86 alla finale clausola WHERE fa sì che la query da eseguire in 292 secondi:

SET @num :=0, @current_shop_id := NULL, @current_product_id := NULL; 

#this query limits the results of the query within it by row number (so that only 250 products get displayed per store) 

SELECT * FROM (

#this query adds row numbers to the query within it 

SELECT *, @num := IF(@current_shop_id = shop_id, IF(@current_product_id=product_id,@num,@num+1), 0) AS row_number, @current_shop_id := shop_id AS shop_dummy, @current_product_id := product_id AS product_dummy FROM (

SELECT shop, shops.shop_id AS 
shop_id, p1.product_id AS 
product_id 
    FROM products p1 LEFT JOIN #this LEFT JOIN gets the favorites count for each product 
    (
    SELECT fav3.product_id AS product_id, SUM(CASE 
    WHEN fav3.current = 1 AND fav3.closeted = 1 THEN 1 
    WHEN fav3.current = 1 AND fav3.closeted = 0 THEN -1 
    ELSE 0 
    END) AS favorites_count 
    FROM favorites fav3 
GROUP BY fav3.product_id 

) AS fav4 ON p1.product_id=fav4.product_id 
    INNER JOIN sex ON sex.product_id=p1.product_id AND 
    sex.sex=0 AND 
    sex.date >= SUBDATE(NOW(),INTERVAL 1 DAY) 
    INNER JOIN shops ON shops.shop_id = p1.shop_id AND 
    shops.shop_id=86 
    ORDER BY shop, sex.DATE, product_id 
    ) AS testtable 

) AS rowed_results WHERE 
rowed_results.row_number>=0 AND 
rowed_results.row_number<(7) 

avrei pensato che limita la tavola negozi con AND shops.shop_id=86 sarebbe ridurre i tempi di esecuzione. Invece, il tempo di esecuzione sembra dipendere dal numero di righe nella tabella prodotti con products.shop_id uguale al negozi.shop_id specificato. Nella tabella prodotti sono presenti circa 34.000 righe con products.shop_id = 86 e il tempo di esecuzione è 292 secondi. Per products.shop_id = 50, ci sono circa 28K righe e il tempo di esecuzione è 210 secondi. Per products.shop_id = 175, ci sono circa 2K righe e il tempo di esecuzione è di 2,8 secondi. Cosa sta succedendo?

spiegare meteo per la seconda query 1.6 sono:

id select_type table type possible_keys key key_len ref rows filtered Extra 
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 1203 100.00 Using where 
2 DERIVED <derived3> ALL NULL NULL NULL NULL 1203 100.00 
3 DERIVED sex ALL product_id_2,product_id NULL NULL NULL 526846 75.00 Using where; Using temporary; Using filesort 
3 DERIVED p1 eq_ref PRIMARY,shop_id,shop_id_2,product_id,shop_id_3 PRIMARY 4 mydatabase.sex.product_id 1 100.00 
3 DERIVED <derived4> ALL NULL NULL NULL NULL 14752 100.00 
3 DERIVED shops eq_ref PRIMARY PRIMARY 4 mydatabase.p1.shop_id 1 100.00 
4 DERIVED fav3 ALL NULL NULL NULL NULL 15356 100.00 Using temporary; Using filesort 

Mostra avvisi per Questo spiega estesa è

-----+ 
| Note | 1003 | select `rowed_results`.`shop` AS `shop`,`rowed_results`.`shop_id` AS `shop_id`,`rowed_results`.`product_id` AS `product_id`,`rowed_results`.`row_number` AS `row_number`,`rowed_results`.`shop_dummy` AS `shop_dummy`,`rowed_results`.`product_dummy` AS `product_dummy` from (select `testtable`.`shop` AS `shop`,`testtable`.`shop_id` AS `shop_id`,`testtable`.`product_id` AS `product_id`,(@num:=if(((@current_shop_id) = `testtable`.`shop_id`),if(((@current_product_id) = `testtable`.`product_id`),(@num),((@num) + 1)),0)) AS `row_number`,(@current_shop_id:=`testtable`.`shop_id`) AS `shop_dummy`,(@current_product_id:=`testtable`.`product_id`) AS `product_dummy` from (select `mydatabase`.`shops`.`shop` AS `shop`,`mydatabase`.`shops`.`shop_id` AS `shop_id`,`mydatabase`.`p1`.`product_id` AS `product_id` from `mydatabase`.`products` `p1` left join (select `mydatabase`.`fav3`.`product_id` AS `product_id`,sum((case when ((`mydatabase`.`fav3`.`current` = 1) and (`mydatabase`.`fav3`.`closeted` = 1)) then 1 when ((`mydatabase`.`fav3`.`current` = 1) and (`mydatabase`.`fav3`.`closeted` = 0)) then -(1) else 0 end)) AS `favorites_count` from `mydatabase`.`favorites` `fav3` group by `mydatabase`.`fav3`.`product_id`) `fav4` on(((`mydatabase`.`p1`.`product_id` = `mydatabase`.`sex`.`product_id`) and (`fav4`.`product_id` = `mydatabase`.`sex`.`product_id`))) join `mydatabase`.`sex` join `mydatabase`.`shops` where ((`mydatabase`.`sex`.`sex` = 0) and (`mydatabase`.`p1`.`product_id` = `mydatabase`.`sex`.`product_id`) and (`mydatabase`.`shops`.`shop_id` = `mydatabase`.`p1`.`shop_id`) and (`mydatabase`.`sex`.`date` >= (now() - interval 1 day))) order by `mydatabase`.`shops`.`shop`,`mydatabase`.`sex`.`date`,`mydatabase`.`p1`.`product_id`) `testtable`) `rowed_results` where ((`rowed_results`.`row_number` >= 0) and (`rowed_results`.`row_number` < 7)) | 
+------ 

spiegare meteo per la seconda query 292 è:

id select_type table type possible_keys key key_len ref rows filtered Extra 
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 36 100.00 Using where 
2 DERIVED <derived3> ALL NULL NULL NULL NULL 36 100.00 
3 DERIVED shops const PRIMARY PRIMARY 4  1 100.00 Using temporary; Using filesort 
3 DERIVED p1 ref PRIMARY,shop_id,shop_id_2,product_id,shop_id_3 shop_id 4  11799 100.00 
3 DERIVED <derived4> ALL NULL NULL NULL NULL 14752 100.00 
3 DERIVED sex eq_ref product_id_2,product_id product_id_2 5 mydatabase.p1.product_id 1 100.00 Using where 
4 DERIVED fav3 ALL NULL NULL NULL NULL 15356 100.00 Using temporary; Using filesort 

SHOW WARNINGS per questo EXPLAIN EXTENDED è

----+ 
| Note | 1003 | select `rowed_results`.`shop` AS `shop`,`rowed_results`.`shop_id` AS `shop_id`,`rowed_results`.`product_id` AS `product_id`,`rowed_results`.`row_number` AS `row_number`,`rowed_results`.`shop_dummy` AS `shop_dummy`,`rowed_results`.`product_dummy` AS `product_dummy` from (select `testtable`.`shop` AS `shop`,`testtable`.`shop_id` AS `shop_id`,`testtable`.`product_id` AS `product_id`,(@num:=if(((@current_shop_id) = `testtable`.`shop_id`),if(((@current_product_id) = `testtable`.`product_id`),(@num),((@num) + 1)),0)) AS `row_number`,(@current_shop_id:=`testtable`.`shop_id`) AS `shop_dummy`,(@current_product_id:=`testtable`.`product_id`) AS `product_dummy` from (select 'shop.nordstrom.com' AS `shop`,'86' AS `shop_id`,`mydatabase`.`p1`.`product_id` AS `product_id` from `mydatabase`.`products` `p1` left join (select `mydatabase`.`fav3`.`product_id` AS `product_id`,sum((case when ((`mydatabase`.`fav3`.`current` = 1) and (`mydatabase`.`fav3`.`closeted` = 1)) then 1 when ((`mydatabase`.`fav3`.`current` = 1) and (`mydatabase`.`fav3`.`closeted` = 0)) then -(1) else 0 end)) AS `favorites_count` from `mydatabase`.`favorites` `fav3` group by `mydatabase`.`fav3`.`product_id`) `fav4` on(((`fav4`.`product_id` = `mydatabase`.`p1`.`product_id`) and (`mydatabase`.`sex`.`product_id` = `mydatabase`.`p1`.`product_id`))) join `mydatabase`.`sex` join `mydatabase`.`shops` where ((`mydatabase`.`sex`.`sex` = 0) and (`mydatabase`.`sex`.`product_id` = `mydatabase`.`p1`.`product_id`) and (`mydatabase`.`p1`.`shop_id` = 86) and (`mydatabase`.`sex`.`date` >= (now() - interval 1 day))) order by 'shop.nordstrom.com',`mydatabase`.`sex`.`date`,`mydatabase`.`p1`.`product_id`) `testtable`) `rowed_results` where ((`rowed_results`.`row_number` >= 0) and (`rowed_results`.`row_number` < 7)) | 
+----- 

Sto eseguendo la versione client MySQL: 5.1.56. La tabella di negozi ha un indice primario su shop_id:

Action Keyname Type Unique Packed Column Cardinality Collation Null Comment 
Edit Drop PRIMARY BTREE Yes No shop_id 163 A 

ho analizzato la tabella negozio, ma questo non ha aiutato.

Ho notato che se rimuovo lo LEFT JOIN la differenza nei tempi di esecuzione scende a 0,12 secondi rispetto a 0,28 secondi.

La soluzione di Cez, ovvero utilizzare la versione 1.6 secondi della query e rimuovere risultati irrilevanti aggiungendo rowed_results.shop_dummy=86 alla query esterna (come di seguito), viene eseguita in 1,7 secondi. Ciò aggira il problema, ma il mistero rimane il motivo per cui una query di 292 secondi è così lenta.

+1

Qual è l'uscita di "spiegano EXTENDED .. " seguito da "MOSTRA AVVERTENZE"? – Cez

+0

... per entrambe le versioni della dichiarazione? Confronta le uscite di 'EXPLAIN' per vedere qual è la differenza. Inoltre tutte le colonne in una singola clausola 'WHERE' dovrebbero entrare in una chiave. – feeela

+0

@Cez @feela grazie per il promemoria, ho aggiunto le query "EXPLAIN EXTENDED' alla domanda. 'SHOW WARNINGS' non ha prodotto risultati. – jela

risposta

1

Dopo la chat room, e in realtà la creazione di tabelle/colonne per abbinare la query, ho trovato la seguente query.

Ho iniziato la mia query più interna per essere sul sesso, sul prodotto (per shop_id) e sulla tabella dei preferiti. Poiché hai descritto che ProductX in ShopA = ID prodotto = 1 ma lo stesso prodottoX in ShopB = ID prodotto = 2 (solo esempio), ogni prodotto è SEMPRE univoco per negozio e mai duplicato. Detto questo, posso ottenere il prodotto e shop_id CON il conteggio dei preferiti (se esiste) a questa query, ma raggruppare solo su product_id .. come shop_id non cambierà per prodotto Sto usando MAX(). Dato che guardi sempre con una data di "ieri" e sesso (sesso = 0 femminile), avrei indicizzato la tabella SESSO (data, sesso, product_id) ...Immagino che tu non aggiunga 1000 di articoli ogni giorno ... I prodotti ovviamente avrebbero un indice su product_id (chiave primaria), e i preferiti DOVREBBERO avere un indice su product_id.

Da quel risultato (alias "sxFav") possiamo quindi fare un join diretto alla tabella sesso e prodotti da quel "ID prodotto" per ottenere tutte le informazioni aggiuntive che potreste volere, come nome del negozio, data del prodotto aggiunto, descrizione del prodotto, ecc. Questo risultato viene quindi ordinato dal negozio_id il prodotto viene venduto da, data e, infine, ID prodotto (ma si può prendere in considerazione l'acquisizione di una colonna di descrizione nella query interna e utilizzarla come ordinamento). Questo risulta in alias "PreQuery".

Con l'ordine che è tutto appropriato per negozio, ora possiamo aggiungere i riferimenti a MySQLQualsiasi volta che ciascun prodotto ha assegnato un numero di riga simile a quello che si è tentato in origine. Tuttavia, resetta a 1 solo quando cambia l'ID di un negozio.

SELECT 
     PreQuery.*, 
     @num := IF(@current_shop_id = PreQuery.shop_id, @num +1, 1) AS RowPerShop, 
     @current_shop_id := PreQuery.shop_id AS shop_dummy 
    from 
     (SELECT 
       sxFav.product_id, 
       sxFav.shop_id, 
       sxFav.Favorites_Count 
      from 
       (SELECT 
         sex.product_id, 
         MAX(p.shop_id) shop_id, 
         SUM(CASE WHEN F.current = 1 AND F.closeted = 1 THEN 1 
           WHEN F.current = 1 AND F.closeted = 0 THEN -1 
           ELSE 0 END) AS favorites_count 
        from 
         sex 
         JOIN products p 
          ON sex.Product_ID = p.Product_ID 
         LEFT JOIN Favorites F 
          ON sex.product_id = F.product_ID 
        where 
          sex.date >= subdate(now(), interval 1 day) 
         and sex.sex = 0 
        group by 
         sex.product_id) sxFav 

       JOIN sex 
       ON sxFav.Product_ID = sex.Product_ID 

       JOIN products p 
       ON sxFav.Product_ID = p.Product_ID 
     order by 
     sxFav.shop_id, 
     sex.date, 
     sxFav.product_id) PreQuery, 

    (select @num :=0, 
       @current_shop_id := 0) as SQLVars 

Ora, se siete alla ricerca di specifiche informazioni "paging" (come ad esempio 7 voci per negozio), avvolgere la query INTERO sopra in qualcosa di simile ...

select * from (entire query above) where RowPerShop between 1 and 7 

(o tra 8 e 14, 15 e 21, ecc come necessario) o addirittura

RowPerShop between RowsPerPage*PageYouAreShowing and RowsPerPage*(PageYouAreShowing +1) 
+0

Ho lavorato su questo per un po '. Questa query è MOLTO più veloce della mia: 0,13 secondi contro 3,4 secondi per la mia query originale (su un set di dati un po 'più grande di quello che usavo in precedenza). Sono confuso riguardo alla clausola 'JOIN sex ON sxFav.Product_ID = sex.Product_ID'. L'aggiunta di questa clausola sembra recuperare risultati non desiderati, perché l'intento della query è di recuperare solo i risultati per 'sex.sex = 0', ma quella clausola aggiungerà anche le righe con' sex.sex = 1', a patto che ci sia una corrispondenza per 'sxFav.Product_ID = sex.Product_ID'. Ho verificato che questo ha aggiunto 11 righe extra non restituite dalla mia query originale. – jela

+0

rimuovendo 'JOIN sex ON sxFav.Product_ID = sex.Product_ID' dalla tua query lo fa recuperare lo stesso numero di risultati della mia query originale (nessun risultato con' sex.sex = 1'). Sono anche confuso riguardo 'JOIN products p ON sxFav.Product_ID = p.Product_ID', dato che questo JOIN ha già luogo quando si crea sxFav, quindi sembra che potrei semplicemente selezionare le colonne rilevanti dalla tabella prodotti all'interno della tabella sxFav. La rimozione di questa clausola non sembra modificare il tempo di esecuzione. Potrei fraintendere ciò che stanno facendo queste due clausole. – jela

+0

@jela, continua in una chat room? Seguirò. – DRapp

1

A giudicare dalla discussione, il pianificatore di query si comporta male quando si specifica il negozio a un livello inferiore.

Aggiungere rowed_results.shop_dummy=86 alla query esterna per ottenere i risultati desiderati.

+0

Questo risolve il problema impiegando la query esterna per eliminare risultati irrilevanti restituiti dalla query interna. Per il momento lascerò aperta la domanda nella speranza che qualcuno possa suggerire una riformulazione della query interna che restituisce solo i risultati richiesti ed è eseguita in modo più efficiente rispetto alla mia versione da 292 secondi. – jela

1

si dovrebbe spostare lo shops.shop_id = 86 alla condizione JOIN per negozi. Non c'è ragione di metterlo fuori dal JOIN, si corre il rischio di MySQL JOINing prima, quindi di filtrare. Un JOIN può fare lo stesso lavoro della clausola WHERE, specialmente se non si fa riferimento ad altre tabelle.

.... 
INNER JOIN shops ON shops.shop_id = p1.shop_id AND shops.shop_id=86 
.... 

Stessa cosa con l'altro sesso si uniscono:

... 
INNER JOIN shops ON shops.shop_id = p1.shop_id 
AND sex.date >= SUBDATE(NOW(),INTERVAL 1 DAY) 
... 

Le tabelle derivate sono grandi, ma non hanno gli indici su di loro. Di solito questo non importa dal momento che sono generalmente in RAM. Ma tra il filtraggio e l'ordinamento senza indici, le cose possono sommarsi.

Si noti che nella seconda query che richiede molto più tempo, l'ordine di elaborazione della tabella cambia. La tabella negozio è in cima alla query lenta e la tabella p1 recupera 11799 righe anziché 1 riga nella query veloce. Inoltre non usa più la chiave primaria. Questo è probabilmente il tuo problema.

3 DERIVED p1 eq_ref PRIMARY,shop_id,shop_id_2,product_id,shop_id_3 PRIMARY 4 mydatabase.sex.product_id 1 100.00 

3 DERIVED p1 ref PRIMARY,shop_id,shop_id_2,product_id,shop_id_3 shop_id 4  11799 100.00 
+0

Ho modificato le condizioni INNER JOIN e rimosso la clausola WHERE. La query eseguita in 283 secondi. Mi chiedo come posso forzare la query lenta a utilizzare correttamente la chiave primaria. – jela

+0

Ho aggiornato la domanda originale con la modifica consigliata alla condizione JOIN. Inoltre ho osservato che il tempo di esecuzione della query sembra dipendere dal numero di righe nella tabella prodotti con products.shop_id uguale al negozi.shop_id specificato. Nella tabella prodotti sono presenti circa 34.000 righe con products.shop_id = 86 e il tempo di esecuzione è 292 secondi. Per products.shop_id = 50, ci sono circa 28K righe e il tempo di esecuzione è 210 secondi. Per products.shop_id = 175, ci sono circa 2K righe e il tempo di esecuzione è di 2,8 secondi. Non sono sicuro di come modificare la query per correggere questo comportamento. – jela

+0

Con quella drastica differenza di velocità per la stessa query, la mia prima ipotesi sarebbe che il tuo sort_buffer_size sia troppo piccolo. MySQL ha drastiche riduzioni delle prestazioni quando non è abbastanza grande. –

Problemi correlati