使用一对多关系连接和搜索多个MySQL表

时间:2009-07-24 00:17:40

标签: php mysql join performance

我一直疯狂地谷歌搜索试图解决这个问题,运气惊人;我猜这是一个常见的问题。

我有5个表:订单,地址,备注,交易,line_items和货件。

transactionsaddressesnotes都已编入索引order_id个字段 - line_itemsshipments已编入索引transaction_id个字段。

我获得的最佳单一查询性能完全站不住脚 - 有时超过30秒。令人沮丧的是,我可以使用1中的大块PHP代码来执行此操作。例如,我将遍历所有注释以匹配给定的搜索,从而将所有order_id保存在数组中。然后我会为所有其他表做同样的事情。然后我将在订单表的最终查询中附加一个大量的IN(...)语句。这很好,但我知道我可以做得更好。

最明显的路线不起作用;只需将所有这些表左键连接到原始订单表和GROUPing BY order.id需要太长时间 - 大约9秒。

对于我的生活,我无法看到我的janky PHP解决方案如何更高效,mysql在内部进行所有这些计算。

我已经多次重写了这些,我几乎无法回忆起我尝试过的所有不同的事情......我认为这是我的第一次尝试:

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结果 9.6895699501秒

我不确定格式化对此有多准确,但我还附上了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

很多,非常感谢。

[编辑:供参考,这是PHP解决方案需要~0.02s - 我怎么能在直接的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:再看一遍;仍然没有找到更好的解决方案。在“命令o”之后添加“FORCE INDEX(PRIMARY)”的评论中的建议一直在敲响几秒钟 - 但我从未真正理解为什么。此外,我已经意识到我的PHP解决方案存在一个限制,即多个术语的搜索仅在表中而不是在表中匹配。

3 个答案:

答案 0 :(得分:2)

你的EXPLAIN的第一行跳出了我。您是否将o.id字段设置为主要唯一键?

确保您的密钥/索引设置正确可以通过huuuuuge幅度减少查询时间(将服务器崩溃转换为1秒响应)

另外,我会通过对CONCAT执行LIKE来简化比较逻辑:

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

答案 1 :(得分:1)

这是您当前简化的WHERE子句:

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

您需要认真查看您正在查找的列 - 这是我不应该在WHERE子句中的列表:

  • 国家
  • 城市
  • 区域
  • 邮编
  • PP-TXN-ID
  • 易趣项-ID
  • 易趣-事务id
  • 易趣-订单ID
  • partnum
  • PART_ID

答案 2 :(得分:1)

如果您真的对用户指定的字符串进行了大量查询,这些字符串是某些字段的子集,我会考虑创建一个full-text index,MySQL支持MyISAM表。