2011-11-21 14 views
11

ho studiato questo, ma ancora non può spiegare perché:Perché query MySQL, LEFT JOIN 'decisamente' più velocemente di quanto la mia inner join

SELECT cl.`cl_boolean`, l.`l_name` 
FROM `card_legality` cl 
INNER JOIN `legality` l ON l.`legality_id` = cl.`legality_id` 
WHERE cl.`card_id` = 23155 

è significativamente più lenta rispetto:

SELECT cl.`cl_boolean`, l.`l_name` 
FROM `card_legality` cl 
LEFT JOIN `legality` l ON l.`legality_id` = cl.`legality_id` 
WHERE cl.`card_id` = 23155 

115 ms vs 478 ms. Stanno entrambi usando InnoDB e ci sono relazioni definite. 'Card_legality' contiene circa 200k righe, mentre la tabella 'legality' contiene 11 righe. Ecco la struttura per ogni:

CREATE TABLE `card_legality` (
    `card_id` varchar(8) NOT NULL DEFAULT '', 
    `legality_id` int(3) NOT NULL, 
    `cl_boolean` tinyint(1) NOT NULL, 
    PRIMARY KEY (`card_id`,`legality_id`), 
    KEY `legality_id` (`legality_id`), 
    CONSTRAINT `card_legality_ibfk_2` FOREIGN KEY (`legality_id`) REFERENCES `legality` (`legality_id`), 
    CONSTRAINT `card_legality_ibfk_1` FOREIGN KEY (`card_id`) REFERENCES `card` (`card_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

E:

CREATE TABLE `legality` (
    `legality_id` int(3) NOT NULL AUTO_INCREMENT, 
    `l_name` varchar(16) NOT NULL DEFAULT '', 
    PRIMARY KEY (`legality_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=latin1; 

ho potuto usare semplicemente LEFT JOIN-, ma non sembra giusto ... tutti i pensieri, per favore?

UPDATE: Come richiesto, ho incluso i risultati di spiegare per ciascuno. Avevo eseguirlo in precedenza, ma io non pretendo di avere una conoscenza approfondita di esso ..

id select_type table type possible_keys key key_len ref rows Extra 
1 SIMPLE cl ALL PRIMARY NULL NULL NULL 199747 Using where 
1 SIMPLE l eq_ref PRIMARY PRIMARY 4 hexproof.co.uk.cl.legality_id 1 

E, join interno:

id select_type table type possible_keys key key_len   ref       rows Extra 
1 SIMPLE l ALL PRIMARY NULL NULL NULL 11 
1 SIMPLE cl ref PRIMARY,legality_id legality_id 4 hexproof.co.uk.l.legality_id 33799 Using where 
+0

A proposito, "card_id" è un VARCHAR poiché non ho scelta, normalmente non lo accetterei. – Ben

risposta

9

È a causa del varchar su card_id. MySQL non può usare l'indice su card_id come card_id come descritto qui mysql type conversion. La parte importante è

per il confronto di una colonna stringa con un numero, MySQL non può utilizzare un indice sulla colonna per cercare il valore rapidamente. Se str_col è una colonna stringa indicizzata , l'indice non può essere utilizzato quando si esegue la ricerca nell'istruzione seguente:

SELECT * FROM nome_tabella WHERE str_col = 1;

La ragione di ciò è che esistono molte stringhe diverse che possono convertire nel valore 1, ad esempio "1", "1" o "1a".

Se si modificano le vostre domande a

SELECT cl.`cl_boolean`, l.`l_name` 
FROM `card_legality` cl 
INNER JOIN `legality` l ON l.`legality_id` = cl.`legality_id` 
WHERE cl.`card_id` = '23155' 

e

SELECT cl.`cl_boolean`, l.`l_name` 
FROM `card_legality` cl 
LEFT JOIN `legality` l ON l.`legality_id` = cl.`legality_id` 
WHERE cl.`card_id` = '23155' 

si dovrebbe vedere un enorme miglioramento in termini di velocità e anche vedere un diverso spiegare.

Ecco un test simile (ma più facile) per mostrare questo:

> desc id_test; 
+-------+------------+------+-----+---------+-------+ 
| Field | Type  | Null | Key | Default | Extra | 
+-------+------------+------+-----+---------+-------+ 
| id | varchar(8) | NO | PRI | NULL |  | 
+-------+------------+------+-----+---------+-------+ 
1 row in set (0.17 sec) 

> select * from id_test; 
+----+ 
| id | 
+----+ 
| 1 | 
| 2 | 
| 3 | 
| 4 | 
| 5 | 
| 6 | 
| 7 | 
| 8 | 
| 9 | 
+----+ 
9 rows in set (0.00 sec) 

> explain select * from id_test where id = 1; 
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra     | 
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ 
| 1 | SIMPLE  | id_test | index | PRIMARY  | PRIMARY | 10  | NULL | 9 | Using where; Using index | 
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ 
1 row in set (0.00 sec) 


> explain select * from id_test where id = '1'; 
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra  | 
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ 
| 1 | SIMPLE  | id_test | const | PRIMARY  | PRIMARY | 10  | const | 1 | Using index | 
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ 
1 row in set (0.00 sec) 

Nel primo caso si ha Using where; Using index e il secondo è Using index. Anche ref è NULL o CONST. Inutile dire che il secondo è migliore.

+0

aha! Brillante! Sei corretto, cercando su 'card_id' come INT, quando in realtà era un VARCHAR ha reso queste query quasi 600 volte più lente di quanto avrebbero dovuto essere! Grazie Andreas :) – Ben

+0

Sì. Ottime informazioni qui, grazie Andreas. – stefgosselin

+0

Ho aggiunto un collegamento dove questo è spiegato. È una buona lettura –

3

mi piacerebbe provare EXPLAIN su entrambe le query. Basta aggiungere un prefisso a SELECT con EXPLAIN ed eseguirli. Fornisce informazioni davvero utili su come mySQL sta ottimizzando ed eseguendo le query.

+3

In base all'argomento in questione, direi che c'è una grande possibilità che l'OP sappia come utilizzare "EXPLAIN". In entrambi i casi questo tipo di informazioni dovrebbe essere inserito in un commento e non in una risposta in quanto non si sta tentando di rispondere alla sua domanda. – Naatan

+0

Ciao L2G, grazie per il tuo commento. Continuerò con la funzione ** SPIEGARE ** e vedere cosa riesco a trovare. Il manuale mysql è in qualche modo carente (o almeno per me). Molte grazie – Ben

0

Sono abbastanza sicuro che MySql ha una migliore ottimizzazione per Left Joins - nessuna prova per eseguire il backup al momento.

ETA: Un giro esploratore veloce e non riesco a trovare nulla di concreto per difendere mio punto di vista così .....

+0

Grazie K.Bob, ho letto simili, ma come te; trovato nessuna prova. – Ben

2

L2G ha praticamente riassunto, anche se ho il sospetto che potrebbe essere a causa del varchar tipo usato per card_id.

In realtà ho stampato this informative page per il benchmarking e il profiling veloce. Ecco una rapida tecnica di profilazione dei poveri:

Time a SQL on MySQL 
Enable Profiling 
mysql> SET PROFILING = 1 
... 
RUN your SQLs 
... 
mysql> SHOW PROFILES; 

+----------+------------+-----------------------+ 
| Query_ID | Duration | Query     | 
+----------+------------+-----------------------+ 
|  1 | 0.00014600 | SELECT DATABASE()  | 
|  2 | 0.00024250 | select user from user | 
+----------+------------+-----------------------+ 
mysql> SHOW PROFILE for QUERY 2; 

+--------------------------------+----------+ 
| Status       | Duration | 
+--------------------------------+----------+ 
| starting      | 0.000034 | 
| checking query cache for query | 0.000033 | 
| checking permissions   | 0.000006 | 
| Opening tables     | 0.000011 | 
| init       | 0.000013 | 
| optimizing      | 0.000004 | 
| executing      | 0.000011 | 
| end       | 0.000004 | 
| query end      | 0.000002 | 
| freeing items     | 0.000026 | 
| logging slow query    | 0.000002 | 
| cleaning up     | 0.000003 | 
+--------------------------------+----------+ 

Buona fortuna, e per favore pubblica i risultati!

+0

Questa informazione è davvero utile stefgosselin, grazie. La tua prognosi era effettivamente corretta, ma la spiegazione di Andreas lo spiegò. Grazie per il tuo aiuto :) – Ben