优化mysql查询以获得更好的性能

时间:2014-07-01 04:24:08

标签: mysql sql

我有以下查询

SELECT o.order_id,
       p.pre_sale_phone_manual_id AS id,
       p.created,
       p.user_id
FROM `order` o
LEFT JOIN `customer` c ON c.customer_id = o.customer_id,
                          `pre_sale_phone_manual` p
LEFT JOIN `pre_sale_phone_manual` p1 ON p.pre_sale_phone_manual_id=p1.pre_sale_phone_manual_id
AND p.created > p1.created
WHERE p1.user_id IS NULL
  AND p.phone <> ""
  AND REPLACE(REPLACE(REPLACE(REPLACE(c.phone, "-", ""), ".", ""), "+", ""), " ", "") LIKE CONCAT('%', RIGHT(REPLACE(REPLACE(REPLACE(REPLACE(p.phone, "-", ""), ".", ""), "+", ""), " ", ""), 10))
  AND o.created > p.created
  AND o.created < (DATE_ADD(p.created, INTERVAL 183 DAY))
  AND o.created > '2013-12-30 08:28:37'

查询基本上是匹配客户的电话号码和pre_sale_phone_manual表中的条目。 pre_sale_phone_manual的记录应该在订单日期之前,并且应该在6个月(183天)之内并且应该与pre_sale_phone_manual表的第一个条目匹配,因为其他用户可能存在重复的条目。

由于我发现订单表和pre_sale_phone_manual表之间的连接速度缓慢,因为没有1到1的连接并扫描整个表格,显然是INTERVAL 183 DAY

以下是EXPLAIN for query

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: o
         type: ALL
possible_keys: order_created_index,fk_order_customer
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 110658
        Extra: Using where
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
         type: ALL
possible_keys: created,phone
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2053
        Extra: Using where; Using join buffer
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: p1
         type: eq_ref
possible_keys: PRIMARY,created
          key: PRIMARY
      key_len: 4
          ref: 463832_yii_adm_t4f.p.pre_sale_phone_manual_id
         rows: 1
        Extra: Using where; Not exists
*************************** 4. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: 463832_yii_adm_t4f.o.customer_id
         rows: 1
        Extra: Using where

以下统计信息来自mysql慢查询日志

Query_time: 126.038395  Lock_time: 0.000303 Rows_sent: 72  Rows_examined: 15266616

以下字段已编入索引,

order.created
pre_sale_phone_manual.created
pre_sale_phone_manual.phone
and PKs and FKs with _id suffix

请帮助您优化查询并感谢您的时间。

5 个答案:

答案 0 :(得分:3)

有一些表演&#34;杀手&#34;:

  1. num-rows-of(customer)* num-rows-of(pre_sale_phone_manual
  2. 的笛卡儿积
  3. 然后c.phone与p.phone的低效方法匹配
  4. 尝试使用左连接
  5. pre_sale_phone_manual中找到每部手机的第一条记录

    (您是否尝试在pre_sale_phone_manual中为每部手机找到第一条记录? 我认为这是代码的作用,所以我认为情况就是如此。)

    我无法轻松解决第2项问题。您的手机专栏似乎无法100%受到信任,但如果此问题得到解决,则查询(我认为)可能是:

    SELECT
          o.order_id
        , p.pre_sale_phone_manual_id AS id
        , p.created
        , p.user_id
    FROM `order` o
          INNER JOIN `customer` c
                ON c.customer_id = o.customer_id
          INNER JOIN (
                SELECT
                      pspm.pre_sale_phone_manual_id AS id
                    , pspm.created
                    , pspm.user_id
                    , pspm.phone
                FROM `pre_sale_phone_manual` pspm
                      INNER JOIN (
                            SELECT
                                  phone
                                , MIN(created) AS created
                            FROM `pre_sale_phone_manual`
                            GROUP BY
                                  phone
                      ) dc
                            ON pspm.created = dc.created 
                            AND pspm.phone = dc.phone
          ) p
                ON c.phone = p.phone /* see notes on this join */
    WHERE o.created > p.created
          AND o.created < DATE_ADD(p.created, INTERVAL 183 DAY)
          AND o.created > '2013-12-30 08:28:37'
    

    手机上的笔记=电话加入(不值得信任的电话栏目)

    查询开发人员可以做的事情不多,除非他们也可以控制表。一种方法是添加可靠的列并索引这些新列。 MySQL没有我所知道的基于函数的索引或计算列,所以你如何获得可靠的数据并不简单。

    This previous question包含可能有用的功能,例如,如果您向客户添加good_phone

     /*
    Function From user1467716
    https://stackoverflow.com/questions/287105/mysql-strip-non-numeric-characters-to-compare
    */
    
    
    CREATE FUNCTION STRIP_NON_DIGIT(input VARCHAR(255))
       RETURNS VARCHAR(255)
    BEGIN
       DECLARE output   VARCHAR(255) DEFAULT '';
       DECLARE iterator INT          DEFAULT 1;
       WHILE iterator < (LENGTH(input) + 1) DO
          IF SUBSTRING(input, iterator, 1) IN ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ) THEN
             SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
          END IF;
          SET iterator = iterator + 1;
       END WHILE;
       RETURN output;
    END
    //
    
    update customer
    set good_phone = strip_non_digit(InputPhone)
    ;
    //
    

    如果您无法解决不可靠的手机数据,那么您将遭受暗示的性能,而不是&#34; phone = phone&#34;你将需要继续:

    AND REPLACE(REPLACE(REPLACE(替换(c.phone,&#34; - &#34;,&#34;&#34;),&#34;。&#34;,&#34; &#34;),&#34; +&#34;,&#34;&#34;),&#34;&#34;,&#34;&#34;)等。

答案 1 :(得分:1)

所以,只是重复别人和我自己已经写过的内容:

  • 您实际上正在使用CROSS JOIN执行昂贵的pre_sale_phone_manual_id。左侧的所有行与右侧的所有行组合。那是一堆行。
  • 尽管LEFT JOIN上有customer,但由于INNER JOIN条件(WHERE条件),您实际上已经在LIKE。{ / LI>
  • 您可以从规范电话号码中受益。
  • 如果条件以通配符“LIKE”开头,则
  • %条件完全无法从索引中受益。 (如果索引足够小以适应PM,它可以在某种程度上受益,因为索引扫描会更快。但它仍然是O(n)而不是O(log(n)))

我假设OUTER JOINCROSS JOIN不是必需的,即你总是在pre_sale_phone_manual_id中有记录,我做了一个微不足道的,显然未经测试的重写。如果假设有效,你可以尝试一下。

SELECT o.order_id,
       p.pre_sale_phone_manual_id AS id,
       p.created,
       p.user_id
FROM `order` o
JOIN `customer` c ON c.customer_id = o.customer_id,
JOIN `pre_sale_phone_manual` p
LEFT JOIN `pre_sale_phone_manual` p1 
    ON p.pre_sale_phone_manual_id=p1.pre_sale_phone_manual_id
    AND p.created > p1.created
WHERE p1.user_id IS NULL
  AND p.phone <> ""
  AND REPLACE(REPLACE(REPLACE(REPLACE(c.phone, "-", ""), ".", ""), "+", ""), " ", "") 
      LIKE CONCAT('%', RIGHT(REPLACE(REPLACE(REPLACE(REPLACE(p.phone, "-", ""), ".", ""), "+", ""), " ", ""), 10))
  AND o.created > p.created
  AND o.created < (DATE_ADD(p.created, INTERVAL 183 DAY))
  AND o.created > '2013-12-30 08:28:37'

因此,由于旧版本中的性能问题,传统上我们更喜欢MySQL中的JOIN。但是,如果您使用NOT EXISTS (...)代替LEFT JOIN ... p1,也可以尝试查看会发生什么。

答案 2 :(得分:1)

首先,你有混合的隐式和显式连接。为了便于阅读,请为pre_sale_phone_manual使用显式的INNER JOIN。这也应该使用ON子句来完成。

此外,您在WHERE子句中引用customer中的列,这似乎使得客户的左连接无关紧要。将其更改为内部联接。

然而,这仍然不会很快。您对pre_sale_phone_manual和订单的加入正在使用DATE_ADD,这将强制对字段进行计算,并可能阻止对该连接使用索引。

同样适用于检查客户和pre_sale_phone_manual表上的电话字段(特别是当您在LIKE上使用前导通配符时)。

每个结果行的pre_sale_phone_manual上有多少条记录?如果数量很大,则可能值得使用子查询来排除除最新查询之外的所有内容。

SELECT o.order_id,
       p.pre_sale_phone_manual_id AS id,
       p.created,
       p.user_id
FROM `order` o
INNER JOIN 
(   
    SELECT pre_sale_phone_manual_id, MAX(created) AS max_created
    FROM `pre_sale_phone_manual`
    GROUP BY pre_sale_phone_manual_id
) p_sub
ON o.created > p_sub.max_created AND o.created < (DATE_ADD(p_sub.max_created, INTERVAL 183 DAY))
INNER JOIN pre_sale_phone_manual p
ON p.pre_sale_phone_manual_id =  p_sub.pre_sale_phone_manual_id
AND p.created =  p_sub.max_created 
INNER JOIN `customer` c ON c.customer_id = o.customer_id
WHERE p.phone <> ""
  AND REPLACE(REPLACE(REPLACE(REPLACE(c.phone, "-", ""), ".", ""), "+", ""), " ", "") LIKE CONCAT('%', RIGHT(REPLACE(REPLACE(REPLACE(REPLACE(p.phone, "-", ""), ".", ""), "+", ""), " ", ""), 10))
  AND o.created > '2013-12-30 08:28:37'

答案 3 :(得分:1)

当一个人没有准确的数据时,调整很难。但无论如何......

  1. 你在pre_sale_phone_manual的两侧同一个coloumn上有一个奇怪的自我加入(!?)。这看起来有点像错误。无论如何,Mysql支持analytic functions,我认为您的自联接可以使用这些转换为单个表访问。

  2. 其他人已经注意到非规范化电话号码的类似情况会受到影响。我建议如下:在INVERSE_PHONEp上添加一列c,其中包含电话号码,但会根据您的选择和从后到前的规范进行标准化({{ 3}})。在p上放置一个索引列,并在where子句中使用它。这基本上取代了基于函数的索引,看起来似乎是maintain it using triggers,但据我所知,它已经消失了。

  3. 如果仍然没有做到这一点,请对(DATE_ADD(p.created, INTERVAL 183 DAY))执行相同的操作,并将p的所有列放在一个在select中使用的索引中。从最具选择性的专栏开始。

  4. 一方面有一个表而另一方有另一个表的所有条件都是连接的一部分,所以将它们放在连接条件而不是where子句中。这有望对性能没有影响,但它使语句更容易阅读。

答案 4 :(得分:0)

我对Oracle更熟悉,但索引怎么样?它们可以大大加快查询速度并避免对表进行全面扫描,尤其是在左外连接处。从explain-output我看到没有使用这样的索引。

尝试放置智能索引。我再次使用Oracle,但我认为mySQL也应该在主键和外键上放置索引。