2011-05-18 66 views
1

我有以下查询(稍作修改为清楚起见)子查询:查询与WHERE子句不断超时

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字段,因为我对索引文本字段犹豫不决。

那我该怎么办?

+0

顺便说一句,我可以看到数据库的设计可能需要审查,因为它似乎不能处理当前的数据量。但我的问题是关于查询的设计,而不是数据库的设计。 – David 2011-05-18 10:47:31

+0

我们正在寻找CaseTasks和FieldChanges的行数。你现在还有什么样的索引。 – JStead 2011-05-18 11:43:05

+0

最好的办法是评估查询计划。这将告诉你,你是否正在进行全盘扫描,你不希望这样做。 – 2011-05-18 11:45:42

回答

0

SET ARITHABORT ON添加到存储过程让它在不到1秒内执行。

我不知道这是什么意思。大概'停止谈论'。

+0

我认为(如果我的旧内存服务于我)这个设置为OFF可能会导致它不考虑执行计划中的索引列。我应该首先注意到存储过程,这是我理解的地方,这很重要。 – 2011-05-18 20:08:30

+0

我也很好奇,看看我建议的ISNULL变化是否有帮助(关闭它) – 2011-05-18 20:09:03

+0

当我得到第二个,我会尝试。谢谢。 – David 2011-05-20 15:15:49

1

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

分享和享受。

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)) 

取决于查询计划到底是什么 - 如果它被执行子查询反复,这个表述可能会有所帮助。

+0

有趣的想法。 – David 2011-05-18 14:53:54

+0

它工作吗? :) – Tao 2011-05-18 15:18:07

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 - 可能不会带来太多好处,但不应该受到伤害,并且可能有助于避免表扫描。使用单场,而不是*只应返回列(我想这不是一个空值列在这里)

更多的想法:多次声明并设置本地值,而不是该得到处理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 
... 

一件事:检查的其中xxx和序列 - 使用最有可能的候选序列,无论条件是首先分离,所以它可以更快地出去。如果这是RecordId,那么找到,如果TableName更好,那么首先使用它。如果一列也有一个索引已经考虑到其他所有条件都相等。

+0

我认为,如果这个子查询是在一个EXISTS子句内,那么SQL Server将会做最低限度的必要来确定是否有子查询返回的记录。 TOP 1会加速吗? – David 2011-05-18 13:26:15

+0

在对此进行第二次考虑后添加了一些附加评论。 – 2011-05-18 19:59:53