非常慢的存储过程

Posted

技术标签:

【中文标题】非常慢的存储过程【英文标题】:Very slow stored procedure 【发布时间】:2011-06-30 23:58:28 【问题描述】:

我在查询优化方面遇到了困难,目前我非常接近重新设计数据库的点。而***是我最后的希望。我认为仅向您显示查询还不够,因此我不仅链接了数据库脚本,还链接了数据库备份,以防您不想手动生成数据

Here你可以找到脚本和备份

当您尝试执行以下操作时,问题就开始了......

exec LockBranches @count=64,@lockedBy='034C0396-5C34-4DDA-8AD5-7E43B373AE5A',@lockedOn='2011-07-01 01:29:43.863',@unlockOn='2011-07-01 01:32:43.863'

这部分出现的主要问题:

UPDATE B
SET B.LockedBy = @lockedBy,
    B.LockedOn = @lockedOn,
    B.UnlockOn = @unlockOn,
    B.Complete = 1
FROM
(
    SELECT TOP (@count) B.LockedBy, B.LockedOn, B.UnlockOn, B.Complete
    FROM Objectives AS O
    INNER JOIN Generations AS G ON G.ObjectiveID = O.ID
    INNER JOIN Branches AS B ON B.GenerationID = G.ID
    INNER JOIN
    (
        SELECT SB.BranchID AS BranchID, SUM(X.SuitableProbes) AS SuitableProbes
        FROM SpicieBranches AS SB
        INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
        INNER JOIN
        (
            SELECT P.ID, 1 AS SuitableProbes
            FROM Probes AS P
/* ----> */ INNER JOIN Results AS R ON P.ID = R.ProbeID /* SSMS Estimated execution plan says this operation is the roughest */
            GROUP BY P.ID
            HAVING COUNT(R.ID) > 0
        ) AS X ON P.ID = X.ID
        GROUP BY SB.BranchID
    ) AS X ON X.BranchID = B.ID
    WHERE
            (O.Active = 1)
        AND (B.Sealed = 0)
        AND (B.GenerationNo < O.BranchGenerations)
        AND (B.LockedBy IS NULL OR DATEDIFF(SECOND, B.UnlockOn, GETDATE()) > 0)
        AND (B.Complete = 1 OR X.SuitableProbes = O.BranchSize * O.EstimateCount * O.ProbeCount)        
) AS B

编辑:这是每个表中的行数:

Spicies         71536
Results         10240
Probes          10240
SpicieBranches  4096
Branches        256
Estimates       5
Generations     1
Versions        1
Objectives      1

【问题讨论】:

已经尝试恢复,但没有 R2 unfort。表ResultsProbes 分别有多少行? 我已编辑问题以显示行数 SqlServer Profiler 为 CPU/Reads/Writes/Duration 提供大约 6300/500000/670/8100 【参考方案1】:

其他人可能比我能更好地解释为什么这要快得多。经验告诉我,当您有一堆查询一起运行很慢但在各个部分应该很快时,那么值得尝试使用临时表。

这样更快

ALTER PROCEDURE LockBranches
-- Add the parameters for the stored procedure here  
@count INT,   
@lockedOn DATETIME,  
@unlockOn DATETIME,  
@lockedBy UNIQUEIDENTIFIER 

AS  
BEGIN  
 -- SET NOCOUNT ON added to prevent extra result sets from  
 -- interfering with SELECT statements.  
 SET NOCOUNT ON  

--Create Temp Table
SELECT SpicieBranches.BranchID AS BranchID, SUM(X.SuitableProbes) AS SuitableProbes 
INTO #BranchSuitableProbeCount
FROM SpicieBranches 
INNER JOIN Probes AS P ON P.SpicieID = SpicieBranches.SpicieID  
INNER JOIN  
(  
     SELECT P.ID, 1 AS SuitableProbes  
     FROM Probes AS P  
     INNER JOIN Results AS R ON P.ID = R.ProbeID  
     GROUP BY P.ID  
     HAVING COUNT(R.ID) > 0  
) AS X ON P.ID = X.ID  
GROUP BY SpicieBranches.BranchID


UPDATE B SET 
B.LockedBy = @lockedBy,    
B.LockedOn = @lockedOn,    
B.UnlockOn = @unlockOn,    
B.Complete = 1
FROM
(
  SELECT TOP (@count) Branches.LockedBy, Branches.LockedOn, Branches.UnlockOn, Branches.Complete  
  FROM Objectives  
  INNER JOIN Generations ON Generations.ObjectiveID = Objectives.ID  
  INNER JOIN Branches ON Branches.GenerationID = Generations.ID  
  INNER JOIN #BranchSuitableProbeCount ON Branches.ID = #BranchSuitableProbeCount.BranchID  
  WHERE  
    (Objectives.Active = 1)  
   AND (Branches.Sealed = 0)  
   AND (Branches.GenerationNo < Objectives.BranchGenerations)  
   AND (Branches.LockedBy IS NULL OR DATEDIFF(SECOND, Branches.UnlockOn, GETDATE()) > 0)  
   AND (Branches.Complete = 1 OR #BranchSuitableProbeCount.SuitableProbes = Objectives.BranchSize * Objectives.EstimateCount * Objectives.ProbeCount)
) AS B

END

平均执行时间为 54 毫秒,而原始执行时间为 6 秒,这要快得多。

编辑

看了一下,并将我的想法与 RBarryYoung 解决方案中的想法结合起来。如果你使用以下创建临时表

SELECT SB.BranchID AS BranchID, COUNT(*) AS SuitableProbes
INTO #BranchSuitableProbeCount  
FROM SpicieBranches AS SB
INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
WHERE EXISTS(SELECT * FROM Results AS R WHERE R.ProbeID = P.ID)
GROUP BY SB.BranchID

然后您可以将其缩短到 15 毫秒,这比我们开始时要快 400 倍。查看执行计划表明临时表上发生了表扫描。通常您会尽可能避免表扫描,但对于 128 行(在这种情况下),它比以前所做的更快。

【讨论】:

请原谅我滥用你的命名约定我发现当一切都被别名时,SQL 真的很难阅读。 嗨大卫,这真的很奇怪,它的运行速度真的非常快,我不明白它怎么会发生......关于约定不是问题,如果你愿意,可以使用你自己的.. . 也通过我的解决方案:),连接不是 100% ,它只是到所需的扩展。从而影响执行计划中的表扫描和其他...【参考方案2】:

这基本上是一个完整的猜测,但过去我发现加入子查询的结果可能非常慢。也就是说,子查询在确实不需要时被评估了太多次。 解决这个问题的方法是将子查询移动到 CTE 中,然后加入这些 CTE。祝你好运!

【讨论】:

【参考方案3】:

两个uniqueidentifier 列上的连接似乎是问题的根源。一个是聚集索引,另一个是非聚集索引(FK表)。很好,它们上有索引。不幸的是,在加入大量行时,guid 的性能是出了名的差。

作为故障排除步骤:

索引处于什么状态?上次更新统计信息是什么时候? 当临时执行时,该子查询对其自身的性能如何?即当您自己运行此语句时,结果集返回的速度有多快?可以接受吗? 重建 2 个索引并更新统计信息后,是否有任何可衡量的差异?
SELECT P.ID, 1 AS SuitableProbes FROM Probes AS P
INNER JOIN Results AS R ON P.ID = R.ProbeID
GROUP BY P.ID  HAVING COUNT(R.ID) > 0

【讨论】:

所有索引都被重建和重组。每一个都有碎片 【参考方案4】:

以下在我的系统上运行速度提高了大约 15 倍:

UPDATE B
SET B.LockedBy = @lockedBy,
    B.LockedOn = @lockedOn,
    B.UnlockOn = @unlockOn,
    B.Complete = 1
FROM
(
    SELECT TOP (@count) B.LockedBy, B.LockedOn, B.UnlockOn, B.Complete
    FROM Objectives AS O
    INNER JOIN Generations AS G ON G.ObjectiveID = O.ID
    INNER JOIN Branches AS B ON B.GenerationID = G.ID
    INNER JOIN 
    (
        SELECT SB.BranchID AS BranchID, COUNT(*) AS SuitableProbes
        FROM SpicieBranches AS SB
        INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
        WHERE EXISTS(SELECT * FROM Results AS R WHERE R.ProbeID = P.ID)
        GROUP BY SB.BranchID
    ) AS X ON X.BranchID = B.ID
    WHERE
            (O.Active = 1)
        AND (B.Sealed = 0)
        AND (B.GenerationNo < O.BranchGenerations)
        AND (B.LockedBy IS NULL OR DATEDIFF(SECOND, B.UnlockOn, GETDATE()) > 0)
        AND (B.Complete = 1 OR X.SuitableProbes = O.BranchSize * O.EstimateCount * O.ProbeCount)        
) AS B

【讨论】:

【参考方案5】:

子查询插入本地临时表

SELECT SB.BranchID AS BranchID, SUM(X.SuitableProbes) AS SuitableProbes
into #temp FROM SpicieBranches AS SB
INNER JOIN Probes AS P ON P.SpicieID = SB.SpicieID
INNER JOIN
(
    SELECT P.ID, 1 AS SuitableProbes
    FROM Probes AS P
/* ----> */ INNER JOIN Results AS R ON P.ID = R.ProbeID /* SSMS Estimated execution plan says this operation is the roughest */
    GROUP BY P.ID
    HAVING COUNT(R.ID) > 0
) AS X ON P.ID = X.ID
GROUP BY SB.BranchID

下面的查询显示了与对应表的部分连接,而不是完整的!!

UPDATE B
SET B.LockedBy = @lockedBy,
    B.LockedOn = @lockedOn,
    B.UnlockOn = @unlockOn,
    B.Complete = 1
FROM
(
    SELECT TOP (@count) B.LockedBy, B.LockedOn, B.UnlockOn, B.Complete
    From
    (
        SELECT ID, BranchGenerations, (BranchSize * EstimateCount * ProbeCount) as MultipliedFactor
        FROM Objectives AS O WHERE (O.Active = 1)
    )O
    INNER JOIN Generations AS G ON G.ObjectiveID = O.ID
    Inner Join
    (
        Select Sealed, GenerationNo, LockedBy, UnlockOn, ID, Complete
        From Branches 
        Where B.Sealed = 0 AND (B.LockedBy IS NULL OR DATEDIFF(SECOND, B.UnlockOn, GETDATE()) > 0)
    )B ON B.GenerationID = G.ID
    INNER JOIN
    (
        Select * from #temp
    ) AS X ON X.BranchID = B.ID
    WHERE
        AND (B.GenerationNo < O.BranchGenerations)
        AND (B.Complete = 1 OR X.SuitableProbes = O.MultipliedFactor)        
) AS B

【讨论】:

除了一些语法差异之外,这与我上面给出的解决方案相同。很高兴看到我们的想法是一样的。 我重复一遍,在我的情况下,连接不是 100% ,它只是到所需的扩展。

以上是关于非常慢的存储过程的主要内容,如果未能解决你的问题,请参考以下文章

转解决存储过程执行快,但C#程序调用执行慢的问题

在SQL Server数据库中执行存储过程很快,在c#中调用很慢的问题

如何优化太慢的存储过程? [关闭]

如何改进包含存储过程使用的多个自联接的视图

SQL(及存储过程)跑得太慢怎么办?

SQL(及存储过程)跑得太慢怎么办?