基于查询计划优化 MySQL 查询的建议

Posted

技术标签:

【中文标题】基于查询计划优化 MySQL 查询的建议【英文标题】:Suggestions to Optimize MySQL Query based on query plan 【发布时间】:2014-11-03 11:06:30 【问题描述】:

这是我在 mysql 数据库上运行的查询。由于订单表中有更多记录,因此查询需要更多时间。请根据查询计划提出优化查询的提示。

mysql> 解释 SELECT DISTINCT dt.customer_id,dt.email,dt.title,dt.fname,dt.lname,dt。 工作电话,dt.mobile_phone,dt.home_phone,dt.blacklist_reason,dt。 域,dt.domain_group,dt.my_account_flag,dt.marketing_preference,dt。 城市,dt.address1,dt.address2,dt.state,dt。 国家,dt.zip,dt.country_code FROM(选择 cc.customer_id,cc.email,cc.title,cc.fname,cc.lname, cc.work_phone,cc.mobile_phone,cc.home_phone,cc.blacklist_reason, cc.domain,cc.domain_group,cc.my_account_flag,cc.marketing_preference, ca.city,ca.address1,ca.address2,ca.state, ca.country,ca.zip,ca.country_code,odd.order_date FROM customer cc INNER JOIN customer_address ca ON cc.customer_id=ca.customer_id left 外连接 order_delivery_details 奇数 cc.customer_id=odd.customer_id WHERE cc.lname = 'XXXXXX' 和 ca.address_purpose='XXXX' 和 ( cc.domain in ('XXXXXX_IE' ) || cc.domain_group in ( 'XXXXX' , 'YYYYYY' ) ) 以奇数.order_date 排序 desc) dt LIMIT 0,500;

SQL:

SELECT 
    DISTINCT dt.customer_id,
    dt.email,
    dt.title,
    dt.fname,
    dt.lname,
    dt. work_phone,
    dt.mobile_phone,
    dt.home_phone,
    dt.blacklist_reason,
    dt. domain,
    dt.domain_group,
    dt.my_account_flag,
    dt.marketing_preference,
    dt. city,
    dt.address1,
    dt.address2,
    dt.state,
    dt. country,
    dt.zip,
    dt.country_code 
    FROM (
        SELECT 
            cc.customer_id,
            cc.email,
            cc.title,
            cc.fname,
            cc.lname, 
            cc.work_phone,
            cc.mobile_phone,
            cc.home_phone,
            cc.blacklist_reason, 
            cc.domain,
            cc.domain_group,
            cc.my_account_flag,
            cc.marketing_preference, 
            ca.city,
            ca.address1,
            ca.address2,
            ca.state, 
            ca.country,
            ca.zip,
            ca.country_code,
            odd.order_date 
        FROM 
            customer cc 
        INNER JOIN customer_address ca ON cc.customer_id=ca.customer_id 
        left outer join order_delivery_details odd on cc.customer_id=odd.customer_id 
        WHERE   cc.lname = 'XXXXXX' 
        and     ca.address_purpose='XXXX'   
        and  ( cc.domain in ( 'XXXXXX_IE' ) 
            or cc.domain_group in  ( 'XXXXX' , 'YYYYYY' ) 
            )   
        order by odd.order_date desc
    ) dt 
    LIMIT 0,500;

感谢您的回复。我无法在子查询之外移动订单,因为主查询不包含 order_date 列。

这是要求。我们应该根据搜索条件搜索客户,并根据客户的最新 order_date 对客户进行排序。客户可以有多个订单,我们要挑选最新订单的order_date,对客户进行排序。

首先,我列出了所有加入订单表的客户,并根据 order_date 对所有记录进行排序。 一旦根据 order_date 对所有记录进行排序,如果客户有多个订单,则很有可能同一客户有多个记录。

现在我在其上应用 distinct (不包括 order_date)以获得明确的客户详细信息。

谢谢, 昌都

【问题讨论】:

ORDER BY 可能应该移到子查询之外。 MySQL 可能目前通过 DISTINCT 保持顺序,但没有其他 RDBMS 可以做到,我怀疑 MySQL 从长远来看会不会。 感谢您的回复。我无法在子查询之外移动订单,因为主查询不包含 order_date 列。 所有订单都没有order_delivery_details表的记录吗?当您订购以获得最新订单时,您是否关心没有订单的客户?那些没有订单的客户将最后排序,因此(有很多客户)极不可能被包含在最新的 500 中,并且内部连接可能会更快。 是的,对于某些客户,订单表中不会有记录。我们还需要考虑客户。所以我使用左外连接而不是内连接。 一个客户平均有多少订单?即,您有 10000 个客户,每个客户有 100 个订单,还是有 100 个客户每个客户有 10000 个订单? 【参考方案1】:

重写查询,使其与您对它应该做什么的描述相匹配:您希望查看按客户最后一个订单排序的客户地址列表。要获得每个地址的条目,您按地址分组。并且要按最后一个订单日期降序排序,您可以按 max(order_date) desc 排序。

SELECT 
  cc.customer_id,
  cc.email,
  cc.title,
  cc.fname,
  cc.lname, 
  cc.work_phone,
  cc.mobile_phone,
  cc.home_phone,
  cc.blacklist_reason, 
  cc.domain,
  cc.domain_group,
  cc.my_account_flag,
  cc.marketing_preference, 
  ca.city,
  ca.address1,
  ca.address2,
  ca.state, 
  ca.country,
  ca.zip,
  ca.country_code
FROM  customer cc 
INNER JOIN customer_address ca ON cc.customer_id = ca.customer_id 
LEFT OUTER JOIN order_delivery_details odd on cc.customer_id = odd.customer_id 
WHERE cc.lname = 'XXXXXX' 
AND ca.address_purpose='XXXX'   
AND (cc.domain in ( 'XXXXXX_IE' ) OR cc.domain_group in ('XXXXX', 'YYYYYY'))   
GROUP BY ca.id
order by max(odd.order_date) desc
LIMIT 0,500;

这应该更快,因为您只需告诉 dbms 您想查看什么,dbms 就能找到实现此目的的最佳方式如何

【讨论】:

另一个想法:可能中间结果太大,因为交付细节太多。完全不加入 order_delivery_details 可能会更好,但 order by (select max(order_date) from order_delivery_details where customer_id = cc.customer_id) desc。可能值得一试。 对不起,可能我说得不好。我的观点是原始查询有一个子查询,它可能为每个客户带来多行。这些按 order_date 排序。但是外部查询使用 DISTINCT 删除重复项;据我了解,MySQL 使用 GROUP BY 处理来执行 DISTINCT,而 GROUP BY 具有隐式 ORDER BY。所以我怀疑它可能会丢失 order_date 顺序中的顺序。您的查询对我来说更有意义,并且似乎符合要求,但可能与原始(慢)查询不匹配。 @Kickstart:好吧,我误会了。您的观察很好,因为 MySQL 文档清楚地表明 GROUP BY 实现 ORDER BY 和 DISTINCT 实现 GROUP BY。因此,DISTINCT 似乎可能会不自觉地破坏 OP 查询中的预构建顺序。然而令人惊讶的是,情况似乎并非如此。这是我的测试:sqlfiddle.com/#!2/5e689e/1 . Kickstart :我最初在设计查询时也有同样的疑问。当我在结果集上使用 distinct 时,幸运的是它保留了按 order by 子句排序的顺序,它达到了我的目的。 我可以建议您尝试@ThorstenKettner 的答案,但删除 OR cc.domain_group in ('XXXXX', 'YYYYYY') 并添加一个涵盖 lname 和的索引领域 。只是对这会快多少感兴趣(因此是否值得将 2 个完全索引的查询限制为 500 个,并将结果合并在一起,然后从那个小结果集中选择最新的 500 个)。【参考方案2】:

看起来它选择在客户表上使用的索引仅用于 lname 字段,但这似乎并没有缩小结果范围。认为您需要添加几个索引,一个涵盖 lname 和 domain,另一个涵盖 lname 和 domain_group。然后你可以有 2 个联合查询。

SELECT 
    DISTINCT dt.customer_id,
    dt.email,
    dt.title,
    dt.fname,
    dt.lname,
    dt. work_phone,
    dt.mobile_phone,
    dt.home_phone,
    dt.blacklist_reason,
    dt. domain,
    dt.domain_group,
    dt.my_account_flag,
    dt.marketing_preference,
    dt. city,
    dt.address1,
    dt.address2,
    dt.state,
    dt. country,
    dt.zip,
    dt.country_code 
    FROM (
        SELECT 
            cc.customer_id,
            cc.email,
            cc.title,
            cc.fname,
            cc.lname, 
            cc.work_phone,
            cc.mobile_phone,
            cc.home_phone,
            cc.blacklist_reason, 
            cc.domain,
            cc.domain_group,
            cc.my_account_flag,
            cc.marketing_preference, 
            ca.city,
            ca.address1,
            ca.address2,
            ca.state, 
            ca.country,
            ca.zip,
            ca.country_code,
            odd.order_date 
        FROM 
            customer cc 
        INNER JOIN customer_address ca ON cc.customer_id=ca.customer_id 
        LEFT OUTER JOIN order_delivery_details odd on cc.customer_id=odd.customer_id 
        WHERE   cc.lname = 'XXXXXX' 
        AND     ca.address_purpose='XXXX'   
        AND  cc.domain_group in  ( 'XXXXX' , 'YYYYYY' )   
        UNION
        SELECT 
            cc.customer_id,
            cc.email,
            cc.title,
            cc.fname,
            cc.lname, 
            cc.work_phone,
            cc.mobile_phone,
            cc.home_phone,
            cc.blacklist_reason, 
            cc.domain,
            cc.domain_group,
            cc.my_account_flag,
            cc.marketing_preference, 
            ca.city,
            ca.address1,
            ca.address2,
            ca.state, 
            ca.country,
            ca.zip,
            ca.country_code,
            odd.order_date 
        FROM 
            customer cc 
        INNER JOIN customer_address ca ON cc.customer_id=ca.customer_id 
        LEFT OUTER JOIN order_delivery_details odd on cc.customer_id=odd.customer_id 
        WHERE   cc.lname = 'XXXXXX' 
        AND     ca.address_purpose='XXXX'   
        AND  cc.domain in ( 'XXXXXX_IE' ) 
        ORDER BY odd.order_date desc
    ) dt 
    LIMIT 0,500;

我不确定您的原始查询需要内部查询和外部查询。 MySQL 将允许您对未返回的列进行排序。但是,主要问题似乎是索引,并且您不能按未从 2 个联合查询返回的列对联合查询的结果进行排序。

【讨论】:

【参考方案3】:

您是否尝试过这样的查询:

SELECT DISTINCT
    cc.customer_id,
    cc.email,
    cc.title,
    cc.fname,
    cc.lname, 
    cc.work_phone,
    cc.mobile_phone,
    cc.home_phone,
    cc.blacklist_reason, 
    cc.domain
    cc.domain_group,
    cc.my_account_flag,
    cc.marketing_preference, 
    ca.city,
    ca.address1,
    ca.address2,
    ca.state, 
    ca.country,
    ca.zip,
    ca.country_code,
FROM 
    customer cc 
INNER JOIN customer_address ca ON cc.customer_id=ca.customer_id 
left outer join order_delivery_details odd on cc.customer_id=odd.customer_id 
WHERE
    cc.lname = 'XXXXXX' 
    AND ca.address_purpose='XXXX'   
    AND ( cc.domain in ( 'XXXXXX_IE' ) OR cc.domain_group in  ( 'XXXXX' , 'YYYYYY' ) )
ORDER BY odd.order_date DESC
LIMIT 0,500

你不需要子选择来获得你想要的。

【讨论】:

我还没试过。明天将尝试一次,并让您知道查询的性能。感谢您的建议。

以上是关于基于查询计划优化 MySQL 查询的建议的主要内容,如果未能解决你的问题,请参考以下文章

基于代价的慢查询优化建议

又一场大咖直播分享会 | MySQL的查询与优化

MYSQL explain执行计划

建议“优化 SQL 查询的响应时间”

mysql优化篇(基于索引)

执行计划的生成