一尘不染

如何优化在嵌套循环(内部联接)上运行缓慢的查询

sql-server

既然这个问题不断得到关注,我就在这里总结一下,让新人不用受历史的折磨:

JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2)    -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa

我意识到这可能不是每个人的问题,但通过强调 ON 子句的敏感性,它可能会帮助您找到正确的方向。无论如何,原始文本是为未来的人类学家准备的:

原文

考虑以下简单查询(仅涉及 3 个表)

    SELECT

        l.sku_id AS ProductId,
        l.is_primary AS IsPrimary,
        v1.category_name AS Category1,
        v2.category_name AS Category2,
        v3.category_name AS Category3,
        v4.category_name AS Category4,
        v5.category_name AS Category5

    FROM category c4
    JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'

    JOIN category c3 ON c3.category_id = c4.parent_category_id
    JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'

    JOIN category c2 ON c2.category_id = c3.category_id
    JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'

    JOIN category c1 ON c1.category_id = c2.parent_category_id
    JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'

    LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
    LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang

    JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
    (
        l.category_id = c4.category_id OR
        l.category_id = c5.category_id
    )

    WHERE c4.[level] = 4 AND c4.version_id = 5

这是一个非常简单的查询,唯一令人困惑的部分是最后一个类别连接,之所以这样是因为类别级别 5 可能存在也可能不存在。在查询结束时,我正在寻找每个产品 ID (SKU ID) 的类别信息,这就是非常大的表 category_link 的来源。最后,表 #Ids 只是一个包含 10‘000 个 ID 的临时表。

执行时,我得到以下实际执行计划:

实际执行计划

如您所见,几乎 90% 的时间都花在了嵌套循环(内连接)上。以下是有关这些嵌套循环的额外信息:

嵌套循环(内连接)

请注意,表名不完全匹配,因为我编辑了查询表名以提高可读性,但它很容易匹配(ads_alt_category = category)。有没有办法优化这个查询?另请注意,在生产中,临时表#Ids 不存在,它是传递给存储过程的相同 10‘000 个 ID 的表值参数。

附加信息:

  • category_id 和 parent_category_id 上的类别索引
  • 在 category_id、language_code 上的 category_voc 索引
  • sku_id、category_id 上的 category_link 索引

编辑(已解决)

正如接受的答案所指出的,问题是 category_link JOIN 中的 OR 子句。但是,接受的答案中建议的代码非常慢,甚至比原始代码还要慢。一个更快更清洁的解决方案是简单地用以下内容替换当前的 JOIN 条件:

JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)

这个微小的调整是最快的解决方案,针对接受的答案中的双重连接进行了测试,并按照 Valverij 的建议针对 CROSS APPLY 进行了测试。


阅读 68

收藏
2022-11-04

共1个答案

一尘不染

问题似乎出在这部分代码中:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

or在加入条件总是可疑的。一个建议是将其拆分为两个连接:

JOIN category_link l1 on l1.sku_id in (SELECT value FROM #Ids) and l1.category_id = cr.category_id
left outer join
category_link l1 on l2.sku_id in (SELECT value FROM #Ids) and l2.category_id = cr.category_id

然后,您必须修改查询的其余部分来处理此问题。. . coalesce(l1.sku_id, l2.sku_id)例如在select子句中。

2022-11-04