2

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

我需要优化这个查询,不知道从哪里开始(除了索引)。

提前致谢!

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; 
4

3 回答 3

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。这将大大降低性能成本。根据经验,我从不要求数据库服务器对字母数字索引执行搜索。

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

希望这可以帮助。祝你好运。

于 2012-11-02T19:47:04.087 回答
1

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

你应该消除这样的事情

UPPER(order_type) = 'AUTOSHIP'

您将这些值用于连接、过滤、分组的位置,因为这将阻止使用字段上的索引。在 SELECT 语句中使用这些 UPPER 函数调用时,您也会损失一些性能(这些在性能方面并不昂贵,因为它们会导致您不使用索引)。如果您的数据经过适当的清理,则您不需要这些。

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

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

于 2012-11-02T19:34:20.317 回答
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是否足够聪明来优化它,所以这可能是一些改进......

基于 VARCHAR 而非 INT 的 JOIN 谓词

这些连接谓词:

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

...如果他们是:他们会表现得更好:

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

还有最重要的问题:误用 LEFT JOIN 会导致笛卡尔积

你肯定在你的关系f和你的关系之间有一个笛卡尔积df,因为你错误地认为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
于 2012-11-03T09:21:32.300 回答