使用左连接的MySQL非常慢的查询

使用左连接的MySQL非常慢的查询,mysql,optimization,group-by,left-join,Mysql,Optimization,Group By,Left Join,我有一个在相当大的数据集上运行的查询。 它非常慢 我需要优化这个查询,但不确定从哪里开始(除了索引) 提前谢谢 SELECT d.distributor_id, d.first_name, d.last_name, d.sponsor_id, COUNT(f.business_level) AS total_enrollments, SUM(CASE WHEN UPPER(f.business_level) = 'EXECUTIVE' THEN 1 else 0 end) AS exe

我有一个在相当大的数据集上运行的查询。
它非常慢

我需要优化这个查询,但不确定从哪里开始(除了索引)

提前谢谢

SELECT d.distributor_id, 
d.first_name,
d.last_name,
d.sponsor_id,
COUNT(f.business_level) AS total_enrollments,
SUM(CASE WHEN UPPER(f.business_level) = 'EXECUTIVE' THEN 1 else 0 end)
    AS executive_enrollments,
SUM(CASE WHEN UPPER(f.business_level) = 'PERSONAL' THEN 1 else 0 end)
    AS personal_enrollments,
SUM(CASE WHEN UPPER(f.business_level) = 'PREFERRED CUSTOMER' THEN 1 else 0 end)
    AS preferred_customer_enrollments,
IFNULL(cf.commission_paid, 0) AS commission_paid,
IFNULL(cf.retention_earned, 0) AS retention_earned,
COUNT(df.order_type) AS total_autoships,
IFNULL(a.consecutive_streak, 0) AS autoship_streak,
IFNULL(a.enrollment_date, "Not Enrolled") AS autoship_enrollment,
d.highest_rank
    FROM warehouse.distributor d
        LEFT JOIN warehouse.enrollment_detail_fact f ON d.distributor_id = f.distributor_id
        LEFT JOIN warehouse.country c ON d.country = c.name
             AND c.country_id = 185
        LEFT JOIN warehouse.autoship a ON d.distributor_id = a.distributor_id
        LEFT JOIN warehouse.order_detail_fact df ON d.distributor_id = df.distributor_id
            AND UPPER(order_type) = 'AUTOSHIP'
            AND date_id IN(SELECT date_id FROM warehouse.date
                WHERE DATE BETWEEN '2012-10-10'
                AND '2012-10-11' ORDER BY date DESC)
        LEFT JOIN warehouse.commission_detail_fact cf ON d.distributor_id = df.distributor_id
        LEFT JOIN db.commission_level_type_details cl ON d.highest_rank = cl.name
WHERE d.active = 1               
    AND cl.commission_level_type_detail_id IN (23)
GROUP BY distributor_id
ORDER BY first_name; 
我不知道你为什么说“除了索引”。这将是我开始寻找优化的第一个地方。用于连接、WHERE子句筛选、分组和排序的每个字段都应该有一个索引。还应明确定义与GROUP BY和ORDER BY中使用的字段关联的表

你应该消除这样的事情

UPPER(order_type) = 'AUTOSHIP'
将这些值用于联接、筛选和分组,因为这将阻止使用字段上的索引。在SELECT语句中使用这些上层函数调用时,也会损失一些性能(这些调用在性能方面并不昂贵,因为它们会导致您不使用索引)。如果您的数据已正确清理,则不需要这些

您可能还应该通过在日期表上进行内部连接并将日期范围过滤器添加到主WHERE子句中来消除该子选择。类似地,在其他情况下,您使用的过滤器可能会作为连接字段进入where子句。如果只是为了查询的可读性,我只会将表连接到适当的键上,并将所有过滤逻辑放在WHERE子句中


看起来您正在处理一个星型架构数据仓库,因此即使在优化索引并删除subselect之后,如果您有大量数据,您的查询可能仍然很慢。

我会尝试将此WHERE子句移动到JOIN子句中:

AND cl.commission_level_type_detail_id IN (23)
LEFT JOIN db.commission_level_type_details cl ON d.highest_rank = cl.name
LEFT JOIN warehouse.order_detail_fact df ON d.distributor_id = df.distributor_id
            AND UPPER(order_type) = 'AUTOSHIP'
            AND date_id IN(SELECT date_id FROM warehouse.date
                WHERE DATE BETWEEN '2012-10-10'
                AND '2012-10-11' ORDER BY date DESC)
将其添加到此JOIN子句中:

AND cl.commission_level_type_detail_id IN (23)
LEFT JOIN db.commission_level_type_details cl ON d.highest_rank = cl.name
LEFT JOIN warehouse.order_detail_fact df ON d.distributor_id = df.distributor_id
            AND UPPER(order_type) = 'AUTOSHIP'
            AND date_id IN(SELECT date_id FROM warehouse.date
                WHERE DATE BETWEEN '2012-10-10'
                AND '2012-10-11' ORDER BY date DESC)
对于此JOIN子句:

AND cl.commission_level_type_detail_id IN (23)
LEFT JOIN db.commission_level_type_details cl ON d.highest_rank = cl.name
LEFT JOIN warehouse.order_detail_fact df ON d.distributor_id = df.distributor_id
            AND UPPER(order_type) = 'AUTOSHIP'
            AND date_id IN(SELECT date_id FROM warehouse.date
                WHERE DATE BETWEEN '2012-10-10'
                AND '2012-10-11' ORDER BY date DESC)
我将把这个数据结构*和上限(order\u type)=“AUTOSHIP”)*规范化为一个“order\u type”表,并改用索引整数ID。效率更高

我还将对日期id进行反规范化(不确定为什么要对记录的日期进行规范化,也许我遗漏了一些业务需求)。只需将日期放在同一个表中,对其进行索引,然后让MySQL做它最擅长的事情。WHERE子句中嵌入的SELECT没有索引,因此MySQL无法以最佳方式处理该数据

事实上,我会规范化JOIN和WHERE子句中所有不是整数的内容。将它们转换为整数ID。这将大大降低性能成本。根据经验,我从不要求DB服务器对字母数字索引执行搜索

我会根据自己的想法编辑和发布更多内容

希望这有帮助。祝你好运。

无用的订单条款 显然,这条ORDER BY条款是完全无用的:

AND date_id IN(SELECT date_id FROM warehouse.date
    WHERE DATE BETWEEN '2012-10-10'
    AND '2012-10-11' ORDER BY date DESC)
                  -- ^^^^^^^^^^^^^^^^^^ remove this!
我不确定MySQL是否足够聪明来优化它,所以这可能是一些改进

基于VARCHAR而不是INT的连接谓词 这些连接谓词:

LEFT JOIN warehouse.country c ON d.country = c.name
。。。如果他们:

LEFT JOIN warehouse.country c ON d.country_id = c.id
最重要的问题是:误用左连接导致笛卡尔积 你的关系
f
df
之间肯定有一个笛卡尔积,因为你错误地
将它们都加入
d
。这意味着,您的查询不仅速度慢,而且可能是错误的。例如:

COUNT(df.order_type) AS total_autoships,
-- [...]
LEFT JOIN warehouse.order_detail_fact df ON d.distributor_id = df.distributor_id
        AND UPPER(order_type) = 'AUTOSHIP'
        AND date_id IN(SELECT date_id FROM warehouse.date
            WHERE DATE BETWEEN '2012-10-10'
            AND '2012-10-11' ORDER BY date DESC)
。。。这可能是错误的。就其本身而言,
COUNT
可能仍然是正确的,但由于您加入了其他1:N关系,
COUNT
可能会爆炸成不现实的值。最好写:

COUNT((SELECT df.order_type
       FROM   warehouse.order_detail_fact df
       WHERE  d.distributor_id = df.distributor_id
       AND    ...)) 
    AS total_autoships
直接连接
聚合值:

df.total_autoships AS total_autoships,
-- [...]
JOIN ( 
    SELECT COUNT(order_type) AS total_autoships 
    FROM   warehouse.order_detail_fact 
    WHERE  d.distributor_id = distributor_id
    AND    ...
) df

我知道数据需要正确索引,我想知道是否有另一种方法来编写这个查询,使它能够得到更好的优化。@Crobzilla如果不了解所有涉及的表的模式,以及它们在现实世界中所代表的内容(因为它与您试图从表中获取的信息有关),这基本上是不可能回答的。太好了问题!谢谢分享。我在回答中更新了我的想法。祝你好运。是的,我已经创建了所需的索引,这就是为什么我列出了“除了索引”。感谢您的输入根据表的名称我猜这是一个用于数据仓库的星型模式数据库(数据仓库的人喜欢将表命名为“事实表”)。在这样的数据仓库中,您会看到一个常见的现象,即使用公共日期维度表处理日期。这使得报告中的各种日期查询条件(周、周、月、季度、年等)的日期聚合更加容易。因此,这可能不是反规范化的候选项(这样做需要在所有表中的这些字段中进行聚合)。。。我感觉这是特定于预期数据应用程序的一些模糊的数据处理需求。我想我们可以做的一个方法是添加非标准化的日期字段以用于报告目的。当然,它们必须更新,但最好只查找一次这些日期,而不是每次需要报告时。或者,可能只是将数据移动到报告数据库或表中,并在复制时进行反规范化。只是大声思考。:-)谢谢你的见解,迈克。我想问题是,这是报告数据库。考虑一些典型的MySQL表,其中包含大量带有时间戳的记录。现在,假设您将该表复制到一个报告数据库中,并希望进行一些分析,以找出全年每周的平均订单数。这将是一个痛苦的查询使用日期时间字段,因为现在有办法查询所有周一,周二等,同时也使用索引。因此,您要做的是像这里所描述的那样构建日期和时间表,然后用日期和/或时间表中相应的id条目替换数据表中的datetime条目。这允许您进行查找