在查询周围添加条件会使时间增加 2400% 以上

Posted

技术标签:

【中文标题】在查询周围添加条件会使时间增加 2400% 以上【英文标题】:Adding Conditional around query increases time by over 2400% 【发布时间】:2016-01-07 18:02:22 【问题描述】:

更新:我会尽快得到查询计划。

我们有一个性能很差的查询,需要 4 分钟才能用于特定组织。在通常的重新编译存储过程和更新统计信息没有帮助之后,我们将 if Exists(...) 重新编写为一个 select count(*)... 以及从 4 分钟到 70 毫秒的存储过程。使 70 毫秒查询需要 4 分钟的条件有什么问题?查看示例

这些都需要 4 分钟以上:

if (
  SELECT COUNT(*)       
    FROM ObservationOrganism  omo
    JOIN Observation          om  ON  om.ObservationID  = omo.ObservationMicID
    JOIN Organism             o   ON  o.OrganismID      = omo.OrganismID
    JOIN ObservationMicDrug   omd ON  omd.ObservationOrganismID = omo.ObservationOrganismID
    JOIN SIRN                 srn ON  srn.SIRNID        = omd.SIRNID
    JOIN OrganismDrug         od  ON  od.OrganismDrugID = omd.OrganismDrugID
  WHERE
    om.StatusCode IN ('F', 'C')
    AND o.OrganismGroupID <> -1
    AND od.OrganismDrugGroupID <> -1
    AND (om.LabType <> 'screen' OR om.LabType IS NULL)) > 0

print 'records';       

-

IF (EXISTS(
  SELECT *       
    FROM ObservationOrganism  omo
    JOIN Observation          om  ON  om.ObservationID  = omo.ObservationMicID
    JOIN Organism             o   ON  o.OrganismID      = omo.OrganismID
    JOIN ObservationMicDrug   omd ON  omd.ObservationOrganismID = omo.ObservationOrganismID
    JOIN SIRN                 srn ON  srn.SIRNID        = omd.SIRNID
    JOIN OrganismDrug         od  ON  od.OrganismDrugID = omd.OrganismDrugID
  WHERE
    om.StatusCode IN ('F', 'C')
    AND o.OrganismGroupID <> -1
    AND od.OrganismDrugGroupID <> -1
    AND (om.LabType <> 'screen' OR om.LabType IS NULL))

print 'records'

这一切都需要 70 毫秒:

Declare @recordCount INT;
SELECT @recordCount = COUNT(*)       
    FROM ObservationOrganism  omo
    JOIN Observation          om  ON  om.ObservationID  = omo.ObservationMicID
    JOIN Organism             o   ON  o.OrganismID      = omo.OrganismID
    JOIN ObservationMicDrug   omd ON  omd.ObservationOrganismID = omo.ObservationOrganismID
    JOIN SIRN                 srn ON  srn.SIRNID        = omd.SIRNID
    JOIN OrganismDrug         od  ON  od.OrganismDrugID = omd.OrganismDrugID
  WHERE
    om.StatusCode IN ('F', 'C')
    AND o.OrganismGroupID <> -1
    AND od.OrganismDrugGroupID <> -1
    AND (om.LabType <> 'screen' OR om.LabType IS NULL);

IF(@recordCount > 0)
  print 'records';

为什么将完全相同的 Count(*) 查询移动到 if 语句中会导致这种退化,或者为什么“存在”比 Count 慢,这对我来说没有意义。我什至在select CASE WHEN Exists() 中尝试了exists(),它仍然是4 多分钟。

【问题讨论】:

您查看过查询计划吗? 你能发布三个例子之间的查询计划吗? 上次查询中的错字? ` AND (om.LabType 'screen' OR om.LabType IS NULL;` 中的未闭合括号 @Ingaz - 修正错字 - 谢谢。 @DanKaufman:丹,请发送计划。你的问题很有趣。 【参考方案1】:

鉴于提到了我之前的答案,我将尝试再次解释,因为这些事情非常棘手。所以是的,我认为您遇到了与the other question 相同的问题。即row goal 问题。

因此,为了尝试解释造成这种情况的原因,我将从引擎可以使用的三种连接类型开始(并且非常明确地):循环连接、合并连接、哈希连接。循环连接就像它们听起来的那样,是一组数据的嵌套循环。 Merge Joins 采用两个排序的列表,并在它们之间同步移动。哈希连接将较小集合中的所有内容放入文件柜,然后在文件柜装满后查找较大集合中的项目。

所以性能方面,循环连接几乎不需要设置,如果您只是在寻找少量数据,它们确实是最佳选择。就任何数据大小的连接性能而言,合并是最好的,但要求数据已经排序(这种情况很少见)。哈希联接需要大量设置,但允许快速联接大型数据集。

现在我们来了解您的查询以及COUNT(*)EXISTS/TOP 1 之间的区别。因此,您看到的行为是优化器认为该查询的行确实很可能(您可以通过在不分组的情况下规划查询并查看它认为在最后一步将获得多少记录来确认这一点)。特别是它可能认为对于该查询中的某个表,该表中的每条记录都将出现在输出中。

“尤里卡!”它说,“如果此表中的每一行都在输出中结束,要查找是否存在,我可以在整个过程中进行非常便宜的启动循环连接,因为即使它对于大型数据集来说很慢,但我只需要一行。”但随后它没有找到该行。而且再也找不到了。而现在,它正在使用其可支配的最不有效的方法对大量数据进行迭代,以清除大量数据。

相比之下,如果您要求数据的完整计数,它必须根据定义找到每条记录。它会查看大量数据并选择最适合迭代整个数据集的选项,而不仅仅是一小部分数据。

另一方面,如果它确实是正确的并且记录之间的相关性非常好,那么它会以尽可能少的服务器资源找到您的记录,并最大限度地提高其整体吞吐量。

【讨论】:

感谢您的解释。阅读这篇文章和你的其他文章,正确解决这个问题的方法是创建一些缺失的索引或重新排序连接等......对吗? 没有办法保证让服务器做你想做的事。除非您有很多其他需要这些字段的查询,否则索引可能会过大。您可以尝试将所有 JOIN 更改为更明确的 INNER HASH JOIN(或为您提供所需行为的最小子集),并且引擎将优先考虑哈希连接,但即便如此,它仍然可以覆盖您的建议,如果它认为有更好的方法。我建议只声明变量,将 count(*) 移出条件并继续个人前进,因为与引擎作斗争是一场艰苦的战斗。 感谢您的建议。我已将其更改为 select Count(),进行了一些冥想,并在每次阅读有关您为什么“从不”使用 Count() 来检查存在的文章时继续努力争取内心的平静。我想我终于继续前进了:)

以上是关于在查询周围添加条件会使时间增加 2400% 以上的主要内容,如果未能解决你的问题,请参考以下文章

如何在已有的SQL查询的语句中,再添加一个查询条件?

SQL语句怎样进行数据库字段的条件查询?

LINQ里的on添加多条件

添加第二个连接条件会以指数方式增加查询时间

在 from 子句 *and* where 子句中添加连接条件使查询更快。为啥?

JAVAEE——BOS物流项目06:分页查询分区导出Excel文件定区添加分页问题总结