基于查询计划优化 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 查询的建议的主要内容,如果未能解决你的问题,请参考以下文章