2009-07-24 14 views
0

Ich habe wild gegoogelt versucht, dies herauszufinden, mit überraschend wenig Glück; Ich denke, das ist ein häufiges Problem.Verknüpfen und Durchsuchen mehrerer MySQL-Tabellen mit Eins-zu-viele-Beziehungen

Ich habe 5 Tabellen: Bestellungen, Adressen, Notizen, Transaktionen, line_items und Sendungen.

transactions, addresses und notes alle haben order_id Felder indiziert - line_items und shipments haben transaction_id Felder indiziert.

Die beste Single-Abfrage-Leistung, die ich bekommen habe, ist völlig unhaltbar - über 30 Sekunden manchmal. Die große und frustrierende Ironie ist, dass ich dies mit einem großen Block PHP-Code unter 1 tun kann. Zum Beispiel werde ich alle Notizen durchgehen, um sie mit einer gegebenen Suche abzugleichen und alle order_ids in einem Array zu speichern. Dann mache ich das gleiche für alle anderen Tische. Dann werde ich eine massive IN (...) Anweisung an meine letzte Abfrage der Tabelle Bestellungen anhängen. Das funktioniert gut, aber ich weiß, dass ich es besser machen kann.

Die offensichtlichsten Routen funktionieren nicht; einfach LINKE JOIN all diese Tabellen zu den ursprünglichen Bestellungen Tabelle und GROUPING von der order.id dauert zu lang - etwa 9 Sekunden.

Für das Leben von mir kann ich nicht sehen, wie meine janky PHP-Lösung effizienter ist, dass mysql all diese Berechnungen intern durchführt.

ich diese so oft neu geschrieben habe, kann ich es kaum, all die verschiedenen Dinge erinnern, die ich versucht habe ... Ich denke, das ist mein erster Versuch war:

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 Ergebnisse 9,6895699501 Sekunden

ich bin nicht sicher, wie genau die Formatierung auf das sein wird, aber ich werde befestigt auch die Erklärung:

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 

vielen, vielen dank.

[Edit: Referenz, hier ist die PHP-Lösung, die ~ 0,02 s dauert - wie kann ich das tun, in gerade mysql !?]

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: bei dieser Suche wieder ; habe immer noch keine bessere Lösung gefunden. Der Vorschlag in den Kommentaren, "FORCE INDEX (PRIMARY)" nach "orders o" einzufügen, hat konsequent ein paar Sekunden abgeschlagen - aber ich habe nie wirklich verstanden warum. Außerdem habe ich festgestellt, dass meine PHP-Lösung dadurch eingeschränkt ist, dass Suchen mit mehreren Begriffen nur innerhalb einer Tabelle und nicht über mehrere Tabellen hinweg abgeglichen werden.

Antwort

2

Die erste Zeile Ihres EXPLAIN springt aus mir heraus. Ist Ihr o.id-Feld als primärer eindeutiger Schlüssel festgelegt?

Gewährleistung Ihre Schlüssel/Indizes eingestellt sind richtig können Ihre Abfragezeit durch huuuuuge Größen (Transformation Server-Abstürze in 1-Sekunden-Antworten)

auch reduzieren, würde ich die Vergleichslogik vereinfachen, indem die LIKE gegen ein tun CONCAT:

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

Ich bin mir nicht sicher, ob ich das CONCAT tun würde. Immer wenn Sie LIKE mit einem berechneten Wert vergleichen, muss der Server nacheinander alle Zeilen durchlaufen. Wenn Sie LIKE mit den direkten Zeilen in der Tabelle vergleichen (und Sie haben eine Art Volltextindex in der Datenbank), kann der Server stattdessen den Index überprüfen. (Wenn es jedoch keinen Volltextindex gibt, müssen Sie alle Zeilen durchgehen, da am Anfang der Zeichenfolge ein% steht.) –

+0

Mit anderen Worten, Sie haben den Text der Abfrage vereinfacht, aber Sie " Die eigentliche Datenbanksuche wurde komplizierter. –

+0

Ja, aber ich gehe davon aus, dass das LIKE fast augenblicklich ist, es sind die Joins, die Verlangsamung verursachen. – rooskie

1

Hier ist Ihre aktuelle WHERE-Klausel vereinfacht:

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%" 

Sie müssen bei ernsten Blick nehmen, was Spalten Klo sind König - hier ist meine Liste derer, die nicht in der WHERE-Klausel sein sollte:

  • Land
  • Stadt
  • Region
  • postal
  • pp-txn-id
  • eBay- Artikel-ID
  • ebay-Transaktion-ID
  • ebay-order-id
  • TEILNUM
  • part_id
+0

Kurioserweise, wenn ich die Abfrage auf nur eine Spalte pro Tabelle begrenzen, dauert es genauso viel Zeit. Wenn ich es auf nur zwei Tabellen auf eine Spalte beschränken, dauert es noch ~ 9.6s. Aber am seltsamsten, wenn ich das zu nur einer Tabelle/einer Spalte ändere, springt es sofort zwei Größenordnungen zu 0.0934s, unabhängig davon, welche Tabelle/Spalte ich wähle. – JKS

1

Wenn Sie wirklich viele Anfragen für Benutzer festgelegten Zeichenketten zu tun, die Teilmengen von einigen Ihrer Felder sind, würde ich auf die Schaffung eines full-text index betrachten suchen, die MySQL für MyISAM unterstützt Tabellen.

Verwandte Themen