我有以下查询(为清楚起见略作修改):
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
字段,因为我对索引文本字段犹豫不决。
那我该怎么办?
答案 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秒的时间内执行。
我不知道这意味着什么。大概是“停止吵架”。