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

时间:2012-11-02 19:29:26

标签: 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 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; 

3 个答案:

答案 0 :(得分:2)

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

AND cl.commission_level_type_detail_id IN (23)

将其添加到此JOIN子句:

LEFT JOIN db.commission_level_type_details cl ON d.highest_rank = cl.name

对于这个JOIN条款:

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)

我会将此数据结构 * AND UPPER(order_type)='AUTOSHIP')* 规范化为“order_type”表,并使用索引整数ID。效率更高。

我还会对date_id进行去规范化(不确定为什么会将记录的日期规范化,也许我错过了一些业务要求)。只需将日期放在同一个表中,将其编入索引并让MySQL尽其所能。在WHERE子句中嵌入的SELECT没有编入索引,因此MySQL无法以最佳方式处理该数据。

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

我会在想到它们时编辑和发布更多内容。

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

答案 1 :(得分:1)

我不知道为什么你说“除了索引”。这将是我开始寻找优化的第一个地方。您用于连接的每个字段,WHERE子句筛选,分组和排序都应该有一个索引。您还应该明确定义与GROUP BY和ORDER BY中使用的字段关联的表。

你应该消除这样的事情

UPPER(order_type) = 'AUTOSHIP'

使用这些值进行连接,过滤,分组的情况,因为这会阻止使用字段上的索引。在SELECT语句中使用这些UPPER函数调用时,您也会失去一些性能(这些函数在性能上并不像它们导致您不使用索引那样昂贵)。如果您的数据已正确清理,则不需要这些。

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

看起来您正在处理星型模式数据仓库,因此即使在优化索引并删除子选择后,如果您有大量数据,您仍然可能会有一个慢查询。

答案 2 :(得分:0)

无用的ORDER BY子句

显然,这个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是否足够智能来优化它,所以这可能会有所改善......

JOIN谓词基于VARCHAR,而不是INT

这些连接谓词:

LEFT JOIN warehouse.country c ON d.country = c.name

......如果是以下情况,他们会表现得更好:

LEFT JOIN warehouse.country c ON d.country_id = c.id

最重要的问题:滥用LEFT JOIN导致笛卡儿产品

你肯定在你的关系fdf之间有笛卡尔积,因为你错误LEFT JOIN他们都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

直接汇总值JOIN

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