【问题标题】:Query with subquery in WHERE clause keeps timing outWHERE 子句中带有子查询的查询不断超时
【发布时间】:2011-08-27 22:54:11
【问题描述】:

我有以下查询(为清楚起见稍作修改):

CREATE PROCEDURE Kctc.CaseTasks_GetCaseTasks
@CaseNumber int
... other parameters
,@ChangedBefore datetime
,@ChangedAfter datetime
AS
SELECT Kctc.CaseTasks.CaseTaskId
  ...blah blah blah
  FROM Kctc.CaseTasks
  ... some joins here
  WHERE  
  ... some normal where clauses
  AND 
  (
    (@ChangedAfter IS NULL AND @ChangedBefore IS NULL)
    OR
    EXISTS (SELECT *
               FROM Kctc.FieldChanges
               WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND 
                     Kctc.FieldChanges.TableName = 'CaseTasks' AND 
                     Kctc.FieldChanges.DateOfChange BETWEEN
                         ISNULL(@ChangedAfter, '2000/01/01') AND
                         ISNULL(@ChangedBefore, '2050/01/01'))
  )

只要用户为@ChangedBefore@ChangedAfter 指定值,此查询就会超时,因此会调用子查询。

子查询检查表中是否存在名为FieldChanges 的记录(它有效地记录了CaseTasks 表中每个字段的更改)。

查询FieldChanges 效率不高,因为它涉及对未编入索引的文本字段TableName 进行过滤。而且我知道子查询本质上是低效的。

所以我的一般问题是,有没有办法重新设计查询以使其性能更好?

当有多个关联的FieldChanges(即保留 EXISTS 语义)时,我想不出一种方法可以将子查询表示为连接,同时仍然只返回一个 CaseTask 行。我还没有索引FieldChanges 表的TableName 字段,因为我对索引文本字段犹豫不决。

那我该怎么办?

【问题讨论】:

  • 顺便说一句,我可以看到可能需要审查数据库的设计,因为它似乎无法应对当前的数据量。但我的问题是关于查询的设计,而不是数据库的设计。
  • 我们在查看 CaseTasks 和 FieldChanges 的行数。还有你目前有什么样的索引。
  • 最好的办法是评估查询计划。这将告诉您是否正在执行您不希望执行的全表扫描。

标签: sql-server performance tsql subquery where-clause


【解决方案1】:

首先,您可以尝试在表 Kctc.FieldChanges 上的 RecordId、TableName 和 DateOfChange 字段(一个包含所有三个字段的单个索引)上放置一个索引,看看是否有帮助。

分享和享受。

【讨论】:

    【解决方案2】:

    不会是一个“好的”解决方案,但它可能比现在发生的更好:

    SELECT Kctc.CaseTasks.CaseTaskId
      ...blah blah blah
      FROM Kctc.CaseTasks
      ... some joins here
      LEFT JOIN (
         SELECT RecordID
         FROM Kctc.FieldChanges
         WHERE Kctc.FieldChanges.TableName = 'CaseTasks'
         AND Kctc.FieldChanges.DateOfChange BETWEEN
                             ISNULL(@ChangedAfter, '2000/01/01') AND
                             ISNULL(@ChangedBefore, '2050/01/01')
         GROUP BY RecordID
      ) AS MatchingChanges ON Kctc.CaseTasks.RecordId = MatchingChanges.RecordId
      WHERE  
      ... some normal where clauses
         AND (MatchingChanges.RecordID Is Not Null OR ((@ChangedAfter IS NULL AND @ChangedBefore IS NULL))
    

    具体取决于查询计划是什么 - 如果它重复执行子查询,这个公式可能会有所帮助。

    【讨论】:

      【解决方案3】:

      我的第一直觉是限制结果集

      SELECT *
      FROM Kctc.FieldChanges
      WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND
          Kctc.FieldChanges.TableName = 'CaseTasks' AND 
          Kctc.FieldChanges.DateOfChange BETWEEN
              ISNULL(@ChangedAfter, '2000/01/01') AND
              ISNULL(@ChangedBefore, '2050/01/01'
      

      改为

      SELECT TOP 1 Kctc.FieldChanges.RecordId 
      FROM Kctc.FieldChanges
      WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND
           Kctc.FieldChanges.TableName = 'CaseTasks' AND 
           Kctc.FieldChanges.DateOfChange BETWEEN
               ISNULL(@ChangedAfter, '2000/01/01') AND
               ISNULL(@ChangedBefore, '2050/01/01'
      

      然后查看where子句中字段的索引

      编辑:关于 TOP 1 - 可能不会带来太多好处,但不应该受到伤害,并且可能有助于避免表扫描。使用单个字段而不是 * 应该只返回该列(我假设它不是 NULL 值列)

      其他想法:声明并设置一个本地值,而不是多次处理的 ISNULL 事物:

      DECLARE @checkmyafter datetime; -- assumption on my part here on the type
      SET @checkmyafter = ISNULL(@ChangedAfter, '2000/01/01');
      

      和之前一样,然后使用

      ...
       SELECT TOP 1 Kctc.FieldChanges.RecordId 
          FROM Kctc.FieldChanges
          WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND
               Kctc.FieldChanges.TableName = 'CaseTasks' AND 
               Kctc.FieldChanges.DateOfChange BETWEEN
                   @checkmybefore  AND  @checkmyafter 
      ...
      

      还有一件事:检查 WHERE xxx AND 的序列 - 使用最有可能的候选者来隔离序列中的第一个,无论是哪种情况,这样它就可以更快地退出。如果那是 RecordId,则查找,如果 TableName 更好,则先使用它。如果一列也有索引,则认为其他所有列都相同。

      【讨论】:

      • 我认为如果此子查询位于 EXISTS 子句中,那么 SQL Server 将执行确定子查询是否返回记录所需的最低限度。 TOP 1 会加快速度吗?
      • 在重新考虑后添加了一些额外的 cmets。
      【解决方案4】:

      在存储过程中添加SET ARITHABORT ON 可以在不到 1 秒的时间内执行。

      我不知道这意味着什么。大概是“停止争论”。

      【讨论】:

      • 我认为(如果我的旧记忆为我服务)此设置为 OFF 可能会导致它不考虑执行计划中的索引列。我应该首先注意到存储过程,据我了解,这很重要。
      • 我也很想知道我建议的 ISNULL 更改是否有帮助(关闭)
      • 等我一秒,我就试试。谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-19
      • 1970-01-01
      • 2016-03-11
      • 2012-01-23
      相关资源
      最近更新 更多