在WHERE子句中使用子查询进行查询可以保持超时

时间:2011-05-18 10:41:27

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

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

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进行过滤。我知道子查询本质上是低效的。

所以我的问题一般是,有没有办法重新设计查询,以便它更好地执行?

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

那我该怎么办?

4 个答案:

答案 0 :(得分:1)

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

分享并享受。

答案 1 :(得分:1)

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

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的序列和 - 使用MOST LIKELY候选者来隔离序列中的FIRST,无论哪种条件,它都可以更快地出来。如果那是RecordId,那么找到,如果TableName更好,请首先使用它。如果一个列也有一个索引已经考虑到其他一切都是相同的。

答案 2 :(得分:0)

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

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 :(得分:0)

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

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