2009-07-24 8 views
0

J'ai été furieusement googling essayer de comprendre cela, avec étonnamment peu de chance; Je suppose que c'est un problème commun.Rejoindre et rechercher plusieurs tables MySQL avec des relations un-à-plusieurs

J'ai 5 tables: ordres, adresses, notes, transactions, éléments de ligne et envois.

transactions, addresses et notes tous ont indexé order_id champs - line_items et shipments ont indexés transaction_id champs.

La meilleure performance de requête unique que j'ai obtenue est complètement indéfendable - parfois plus de 30 secondes. L'ironie du sort est que je peux le faire avec un gros bloc de code PHP en dessous de 1. Par exemple, je vais parcourir toutes les notes pour comparer avec une recherche donnée, en sauvegardant tous les order_ids dans un tableau. Ensuite, je ferai de même pour toutes les autres tables. Ensuite, je vais ajouter une instruction IN (...) massive sur ma requête finale de la table des ordres. Cela fonctionne bien, mais je sais que je peux faire mieux.

Les routes les plus évidentes ne fonctionnent pas; RACCORDER simplement toutes ces tables à la table des commandes d'origine et GROUPING BY à order.id prend trop de temps - environ 9 secondes.

Pour la vie de moi, je ne peux pas voir comment ma solution PHP janky est plus efficace que mysql faire tous ces calculs en interne.

J'ai réécrit ce que je peux tant de fois, à peine rappeler toutes les différentes choses que j'ai essayé ... Je pense que ce fut ma première tentative:

SELECT o.id FROM orders o 
LEFT JOIN addresses a ON a.order_id = o.id 
LEFT JOIN notes n ON (n.parent_id = o.id AND n.type = "parts") 
LEFT JOIN transactions t ON t.order_id = o.id 
LEFT JOIN line_items li ON li.transaction_id = t.id 
LEFT JOIN shipments s ON s.transaction_id = t.id 
WHERE 0 = 0 
AND ((a.`email` LIKE "%Lachman%" || a.`contact_name` LIKE "%Lachman%" || a.`company_name` LIKE "%Lachman%" || a.`address1` LIKE "%Lachman%" || a.`address2` LIKE "%Lachman%" || a.`country` LIKE "%Lachman%" || a.`city` LIKE "%Lachman%" || a.`region` LIKE "%Lachman%" || a.`postal_code` LIKE "%Lachman%" || n.`note` LIKE "%Lachman%" || t.`g_order_number` LIKE "%Lachman%" || t.`pp_txn_id` LIKE "%Lachman%" || t.`fm_invoice_num` LIKE "%Lachman%" || t.`ebay_item_id` LIKE "%Lachman%" || t.`ebay_buyer_id` LIKE "%Lachman%" || t.`ebay_transaction_id` LIKE "%Lachman%" || t.`ebay_order_id` LIKE "%Lachman%" || li.`partnum` LIKE "%Lachman%" || li.`part_id` LIKE "%Lachman%" || li.`desc` LIKE "%Lachman%" || li.`source` LIKE "%Lachman%" || s.`tracking` LIKE "%Lachman%" || s.`carrier` LIKE "%Lachman%")) 
GROUP BY o.id 
ORDER BY `created` DESC 

2 résultats 9,6895699501 secondes

Je ne suis pas sûr de savoir comment la mise en forme précise sera sur ce point, mais je vais aussi attaché le explaination:

id select_type table type possible_keys key key_len ref rows Extra 
1 SIMPLE o ALL NULL NULL NULL NULL 2840 Using temporary; Using filesort 
1 SIMPLE a ref order_id order_id 5 apple_components.o.id 1  
1 SIMPLE n ref parent_id,type type 22 const 314 
1 SIMPLE t ref order_id order_id 5 apple_components.o.id 1  
1 SIMPLE li ref transaction_id transaction_id 4 apple_components.t.id 1  
1 SIMPLE s ref transaction_id transaction_id 4 apple_components.t.id 1 Using where 

Un grand merci.

[Edit: pour référence, voici la solution PHP qui prend ~ 0.02s - comment puis-je faire cela dans une base MySQL droite !?]

if ($s['s']) { 
    $search_fields = array(
     'a' => array('email', 'contact_name', 'company_name', 'address1', 'address2', 'country', 'city', 'region', 'postal_code'), 
     'n' => array('note'), 
     't' => array('g_order_number', 'pp_txn_id', 'fm_invoice_num', 'ebay_item_id', 'ebay_buyer_id', 'ebay_transaction_id', 'ebay_order_id'), 
     'li' => array('partnum', 'part_id', 'desc', 'source'), 
     's' => array('tracking', 'carrier') 
    ); 
    $search_clauses = array(); 
    foreach ($search_fields as $table => $fields) { 
     $the_fields = array(); 
     foreach ($fields as $field) $the_fields[] = $table.'.`'.$field.'`'; 
     $clauses = array(); 
     foreach (explode(' ', $s['s']) as $term) $clauses[] = 'CONCAT_WS(" ", '.implode(', ', $the_fields).') LIKE "%'.$term.'%"'; 
     $search_clauses[$table] = $clauses; 
    } 

    $order_ids = array(); 
    $results = mysql_query('SELECT order_id FROM addresses a WHERE '.implode(' AND ', $search_clauses['a'])); 
    while ($result = mysql_fetch_assoc($results)) $order_ids[] = $result['order_id']; 
    $results = mysql_query('SELECT parent_id FROM notes n WHERE type = "orders" AND '.implode(' AND ', $search_clauses['n'])); 
    while ($result = mysql_fetch_assoc($results)) $order_ids[] = $result['parent_id']; 
    $results = mysql_query('SELECT order_id FROM transactions t WHERE '.implode(' AND ', $search_clauses['t'])); 
    while ($result = mysql_fetch_assoc($results)) $order_ids[] = $result['order_id']; 

    $transaction_ids = array(); 
    $results = mysql_query('SELECT transaction_id FROM line_items li WHERE '.implode(' AND ', $search_clauses['li'])); 
    while ($result = mysql_fetch_assoc($results)) $transaction_ids[] = $result['transaction_id']; 
    $results = mysql_query('SELECT transaction_id FROM shipments s WHERE '.implode(' AND ', $search_clauses['s'])); 
    while ($result = mysql_fetch_assoc($results)) $transaction_ids[] = $result['transaction_id']; 
    if (count($transaction_ids)) { 
     $results = mysql_query('SELECT order_id FROM transactions WHERE id IN ('.implode(', ', $transaction_ids).')'); 
     while ($result = mysql_fetch_assoc($results)) if (!empty($result['order_id'])) $order_ids[] = $result['order_id']; 
    } 
} 
$query = 'SELECT id FROM orders WHERE id IN ('.implode(', ', $order_ids).')'; 

2009-10-07: En regardant ce nouveau ; toujours pas trouvé une meilleure solution. La suggestion dans les commentaires d'ajouter "FORCE INDEX (PRIMARY)" après "orders o" a systématiquement disparu quelques secondes - mais je n'ai jamais vraiment compris pourquoi. Aussi, j'ai réalisé depuis lors qu'il y a une limite dans ma solution PHP dans la mesure où les recherches avec plusieurs termes ne sont comparés qu'au sein d'une table plutôt qu'à travers les tables.

Répondre

2

La première ligne de votre EXPLAIN saute sur moi. Avez-vous votre champ o.id défini comme une clé unique primaire?

Assurer vos clés/index sont configurés correctement peut réduire votre temps d'interrogation par des grandeurs huuuuuge (transformation serveur-accidents dans les réponses 1 seconde)

Aussi, je simplifier la logique de comparaison en faisant LIKE contre une CONCAT:

WHERE CONCAT(
    a.email, 
    a.contactname, 
    .... 
) LIKE "%lachman%" 
+0

Je ne suis pas sûr de faire ce CONCAT. Chaque fois que vous faites LIKE contre une valeur calculée, le serveur doit parcourir toutes les lignes une par une. Si vous faites LIKE contre les lignes directes dans la table (et que vous avez une sorte d'index de texte intégral dans la base de données), le serveur peut vérifier l'index à la place. (bien qu'il n'y ait pas d'index de texte intégral, vous êtes bloqué en parcourant toutes les lignes puisqu'il y a un% au début de la chaîne.) –

+0

En d'autres termes, vous avez simplifié le texte de la requête, mais vous ' J'ai rendu la recherche dans la base de données plus compliquée. –

+0

Oui, mais je suis sous l'hypothèse que le LIKE sera presque instantané, ce sont les jointures qui provoquent le ralentissement. – rooskie

1

Voici votre actuelle clause WHERE simplifiée:

WHERE a.email LIKE "%Lachman%" 
    OR a.contact_name LIKE "%Lachman%" 
    OR a.company_name LIKE "%Lachman%" 
    OR a.address1 LIKE "%Lachman%" 
    OR a.address2 LIKE "%Lachman%" 
    OR a.country LIKE "%Lachman%" 
    OR a.city LIKE "%Lachman%" 
    OR a.region LIKE "%Lachman%" 
    OR a.postal_code LIKE "%Lachman%" 
    OR n.note LIKE "%Lachman%" 
    OR t.g_order_number LIKE "%Lachman%" 
    OR t.pp_txn_id LIKE "%Lachman%" 
    OR t.fm_invoice_num LIKE "%Lachman%" 
    OR t.ebay_item_id LIKE "%Lachman%" 
    OR t.ebay_buyer_id LIKE "%Lachman%" 
    OR t.ebay_transaction_id LIKE "%Lachman%" 
    OR t.ebay_order_id LIKE "%Lachman%" 
    OR li.partnum LIKE "%Lachman%" 
    OR li.part_id LIKE "%Lachman%" 
    OR li.desc LIKE "%Lachman%" 
    OR li.source LIKE "%Lachman%" 
    OR s.tracking LIKE "%Lachman%" 
    OR s.carrier LIKE "%Lachman%" 

vous devez prendre pencher sérieusement sur ce que les colonnes que vous êtes Loo roi - voici ma liste de ceux qui ne devraient pas être dans la clause WHERE:

  • pays
  • ville
  • région
  • postalcode
  • pp-txn-id
  • ebay- item-id
  • ebay-transaction-id
  • ebay-order-id
  • partNum
  • part_id
+0

Curieusement, si je limite la requête à une seule colonne par table, cela prend autant de temps. Si je le limite à une colonne sur seulement deux tables, il prend encore ~ ​​9,6 s. Mais le plus étrange de tous, si je change cela en une seule table/colonne, cela saute immédiatement de deux ordres de grandeur à 0.0934s, indépendamment de la table/colonne que je choisis. – JKS

1

Si vous êtes vraiment faire beaucoup de requêtes pour les chaînes spécifiées par l'utilisateur qui sont des sous-ensembles de certains de vos champs, je considérerais la recherche à créer un full-text index, qui prend en charge MySQL pour MyISAM les tables.

Questions connexes