-1

我有一个 .NET 4.5 程序,它基本上运行一个循环,其中多次执行相同的存储过程,使用相同的SqlConnection. 如果发生命令超时,它会增加并重新运行查询(这是出于测试目的,在实际应用程序中,将修改 SP 的输入参数)。

有问题的存储过程可以在下面找到。基本上它是各种SELECT/DELETE/UPDATE陈述。它以 no or 语句开头SET XACT_ABORT ON并且不包含BEGIN TRANorROLLBACK TRAN语句。

运行该程序一段时间后,查询似乎开始运行得很慢。重新启动程序没有帮助,查询仍然运行缓慢。似乎这种减速发生在发生命令超时之后。但是在 sql server management studio 中运行相同的查询并没有显示出减速。

我进行了一些故障排除,复制了 SQL 并将其粘贴到 SQL Server Management Studio 的查询窗口中,并尝试从那里运行它(在 a 之后CHECKPOINT; DBCC DROPCLEANBUFFERS和 a 内BEGIN TRAN/ROLLBACK TRAN)。它运行了大约 4 秒。连续运行大约需要 0.5 秒。

然后我在一个新的 .NET 控制台应用程序中做了同样的事情,查询运行非常慢,大约 48 秒(这也是 SQL Server 显示的持续时间,所以它实际上是在执行需要这么多时间的查询)。连续运行大约需要 17 秒。

简单地运行一个ALTER命令来更新 SP(不改变它)实际上“解决”了问题,并且查询再次快速运行,直到它们再次开始运行缓慢。

我也尝试过使用SqlConnection.ClearAllPools(),但这似乎没有帮助。

到底是什么导致了这种行为?我想不出 SQL Server 会开始以这种方式运行的任何原因。如果更新 SP,为什么查询会突然开始运行得更快?(以及为什么它首先会变慢,但仅限于来自 .NET 的连接)

编辑: 我现在也尝试了所有可能使用相关数据库的KILLSPID 。sys.sysprocesses有许多系统进程无法被杀死,但这并没有解决问题。 sys.dm_tran_locks显示未持有任何锁(单个共享数据库锁除外)。

编辑 2: 使数据库脱机,然后重新联机似乎也可以解决问题。(直到它再次发生)。但是什么会导致这个问题呢?

以下是用于引发问题的方法:

 static void BasicTest()
  {

     const string connectionString = "Server=.;Initial Catalog=Filler;Integrated Security=SSPI";

     const string sql = @"declare @p3 bigint
      set @p3=NULL
      declare @p4 bigint
      set @p4=NULL
      declare @p5 bigint
      set @p5=NULL
      declare @p6 bigint
      set @p6=NULL
      declare @p7 bigint
      set @p7=NULL
      declare @p8 bit
      set @p8=NULL
      exec dbo.spRunTrackingCleanupBatch @BatchSize=3000,@SessionId=1,@NonDeletableEntityCount=@p3 output,@DeletedNodeCount=@p4 output,@DeletedEventCount=@p5 output,@DeletedJobCount=@p6 output,@DeletedLogEntryCount=@p7 output,@IsComplete=@p8 output
      select @p3, @p4, @p5, @p6, @p7, @p8";

     int commandTimeout = 3;

     using (SqlConnection conn = new SqlConnection(connectionString))
     {
        conn.Open();
        bool isComplete = false;
        while (!isComplete)
        {
           try
           {
              using (SqlTransaction tran = conn.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
              {
                 using (SqlCommand command = conn.CreateCommand())
                 {
                    command.Transaction = tran;
                    command.CommandText = sql;
                    command.CommandType = CommandType.Text;
                    command.CommandTimeout = commandTimeout;

                    Console.WriteLine("Running query...");
                    Stopwatch w = Stopwatch.StartNew();
                    using (IDataReader reader = command.ExecuteReader())
                    {
                       reader.Read();
                       isComplete = reader.GetBoolean(5);
                    }
                    w.Stop();
                    Console.WriteLine("Elapsed: {0}", w.Elapsed);
                    if (w.Elapsed > TimeSpan.FromSeconds(40))
                       throw new Exception("Too slow!!!");                        
                 }

                 tran.Commit();
              }
           }
           catch (SqlException sex)
           {
              if (sex.IsTimeout())
              {
                 Console.WriteLine("TIMEOUT!!!");
                 commandTimeout += 5;
                 conn.Close();
                 conn.Open();
              }
              else
              {
                 throw;
              }
           }
        }
     }
  }

存储过程spRunTrackingCleanupBatch

ALTER PROCEDURE [dbo].[spRunTrackingCleanupBatch]
(
   @SessionId int,            
   @BatchSize int,            
   @IsComplete bit OUTPUT,
   @NonDeletableEntityCount bigint OUTPUT,
   @DeletedNodeCount bigint OUTPUT,
   @DeletedEventCount bigint OUTPUT,
   @DeletedJobCount bigint OUTPUT,
   @DeletedLogEntryCount bigint OUTPUT
)
AS 
BEGIN
   SET XACT_ABORT, NOCOUNT ON;

   SET DEADLOCK_PRIORITY -8;

   IF @@TRANCOUNT = 0
   BEGIN
      RAISERROR(N'spRetrieveEventMatchesForProcessing must be executed in a transaction.', 18, 1)        
      RETURN
   END

   SET @NonDeletableEntityCount=0;
   SET @DeletedNodeCount=0
   SET @DeletedEventCount=0
   SET @DeletedJobCount=0
   SET @DeletedLogEntryCount=0

   DECLARE @DateUpperBound datetime2 
   SELECT @DateUpperBound=fldDateUpperBound FROM tblTrackingCleanupSessions WHERE fldSessionId=@SessionId

   IF @DateUpperBound IS NULL
      RAISERROR(N'The tracking cleanup session with Id %d does not exist.', 18, 1, @SessionId)

   DECLARE @RootEntityKeys tvpTrackingEntityKeyList

   DELETE TC 
      OUTPUT DELETED.fldEntityId, DELETED.fldEntityTypeId INTO @RootEntityKeys
      FROM tblTrackingCleanupCandidates TC
      INNER JOIN (SELECT TOP (@BatchSize) fldEntityId, fldEntityTypeId FROM tblTrackingCleanupCandidates WHERE fldSessionId=@SessionId 
         ORDER BY fldCreationTime DESC
         ) Q 
         ON Q.fldEntityId=TC.fldEntityId AND Q.fldEntityTypeId=TC.fldEntityTypeId AND TC.fldSessionId=@SessionId

   DECLARE @CandidateEntityKeys TABLE
   (
      fldIsCleanupPrevented bit,
      fldTimestamp datetime2,
      fldEntityTypeId tinyint,
      fldEntityId int,
      PRIMARY KEY (fldEntityTypeId, fldEntityId)
   )

   ; WITH
   Base AS
   (
      SELECT Roots.fldEntityTypeId AS fldRootEntityTypeId, Roots.fldId AS fldRootId, C.* FROM @RootEntityKeys Roots
         CROSS APPLY dbo.ft_GetTrackingCleanupTree(Roots.fldId, Roots.fldEntityTypeId) C
   ),
   Agg AS
   (
      SELECT fldRootId, SUM(fldIsTransientState) AS fldIsCleanupPrevented, MAX(fldTimestamp) AS fldMaxTimestamp FROM Base
         GROUP BY fldRootId
   )
   INSERT INTO @CandidateEntityKeys (fldIsCleanupPrevented, fldTimestamp, fldEntityTypeId, fldEntityId)
      SELECT DISTINCT Agg.fldIsCleanupPrevented, MAX(Agg.fldMaxTimestamp), fldEntityTypeId, fldId FROM Base 
         INNER JOIN Agg ON Base.fldRootId=Agg.fldRootId   
         GROUP BY Agg.fldIsCleanupPrevented, fldEntityTypeId, fldId               
   OPTION (MAXRECURSION 32677) 

   SELECT @NonDeletableEntityCount=@@ROWCOUNT

   DECLARE @DeletableKeys tvpTrackingEntityKeyList

   INSERT INTO @DeletableKeys (fldEntityTypeId, fldId)
      SELECT fldEntityTypeId, fldEntityId FROM @CandidateEntityKeys 
      WHERE fldIsCleanupPrevented=0 AND fldTimestamp <= @DateUpperBound

   SELECT @NonDeletableEntityCount=@NonDeletableEntityCount-@@ROWCOUNT

   DELETE TC 
      FROM tblTrackingCleanupCandidates TC
      INNER JOIN (
         SELECT CC.fldEntityTypeId, CC.fldEntityId FROM @CandidateEntityKeys CEK
            OUTER APPLY dbo.fn_GetTrackingEntityParentList(fldEntityTypeId, fldEntityId) CC
            WHERE CEK.fldIsCleanupPrevented<>0 OR CEK.fldTimestamp > @DateUpperBound
      ) Q ON Q.fldEntityId=TC.fldEntityId AND Q.fldEntityTypeId=TC.fldEntityTypeId
      WHERE TC.fldSessionId=@SessionId

   EXEC dbo.spDeleteTrackingEntities @DeletableKeys,@DeletedNodeCount OUTPUT, @DeletedJobCount OUTPUT, @DeletedLogEntryCount OUTPUT, @DeletedEventCount OUTPUT

   IF EXISTS(SELECT * FROM tblTrackingCleanupCandidates WHERE fldSessionId=@SessionId)
      SET @IsComplete = 0
   ELSE
      SET @IsComplete = 1

END

存储过程spDeleteTrackingEntities

CREATE PROCEDURE [dbo].[spDeleteTrackingEntities]
   @EntityKeys AS tvpTrackingEntityKeyList READONLY,
   @DeletedNodeCount bigint OUTPUT,
   @DeletedJobCount bigint OUTPUT,
   @DeletedLogEntryCount bigint OUTPUT,
   @DeletedEventCount bigint OUTPUT  
AS
BEGIN
    SET XACT_ABORT, NOCOUNT ON;
   -- Job = 59, Event = 19, Node=18, Log=20
   SET DEADLOCK_PRIORITY LOW;

   IF @@TRANCOUNT = 0
   BEGIN
      RAISERROR(N'spRetrieveEventMatchesForProcessing must be executed in a transaction.', 18, 1)
      RETURN
   END

   CREATE TABLE #DeletedEntityKeys
   (
      fldEntityTypeId tinyint,
      fldId bigint,
      CONSTRAINT PK_DeletedEntityKeys PRIMARY KEY (fldEntityTypeId, fldId)
   )

   CREATE TABLE #DeletedArgumentRefs 
   (
      fldArgumentId bigint NOT NULL
   )

   SET @DeletedEventCount=0
   SET @DeletedJobCount=0
   SET @DeletedLogEntryCount=0
   SET @DeletedNodeCount=0

   DELETE TR FROM tblTrackingReferences TR
      INNER JOIN @EntityKeys EK ON (TR.fldRefereeId=EK.fldId AND TR.fldRefereeType=EK.fldEntityTypeId) 

   DELETE TR FROM tblTrackingReferences TR
      INNER JOIN @EntityKeys EK ON (TR.fldReferrerId=EK.fldId AND TR.fldRefereeId=EK.fldEntityTypeId)

   DELETE L 
      OUTPUT 20, DELETED.fldLogId INTO #DeletedEntityKeys
      FROM dbo.tblLog L
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=20 AND EK.fldId=L.fldLogId

   SELECT @DeletedLogEntryCount=@@ROWCOUNT

   DELETE NR
      FROM tblNodeRelation NR
      INNER JOIN tblNode N ON (NR.fldNodeId=N.fldNodeId OR NR.fldRelNodeId=N.fldNodeId)
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=18 AND (EK.fldId=NR.fldNodeId OR EK.fldId=NR.fldRelNodeId)
      WHERE N.fldRetain=0

   DELETE N 
      OUTPUT 18, DELETED.fldNodeId INTO #DeletedEntityKeys
      FROM dbo.tblNode N
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=18 AND EK.fldId=N.fldNodeId
      WHERE N.fldRetain=0

   SELECT @DeletedNodeCount=@@ROWCOUNT

   DELETE TR FROM dbo.tblTrackingReferences TR 
      INNER JOIN dbo.tblLog L ON (TR.fldRefereeId=L.fldLogId AND TR.fldRefereeType=20) 
      INNER JOIN dbo.tblJobs J ON J.fldJobId=L.fldJobId
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=J.fldJobId

   DELETE TR FROM dbo.tblTrackingReferences TR 
      INNER JOIN dbo.tblLog L ON (TR.fldReferrerId=L.fldLogId AND TR.fldReferrerType=20)         
      INNER JOIN dbo.tblJobs J ON J.fldJobId=L.fldJobId
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=J.fldJobId

   DELETE L 
      OUTPUT 20, DELETED.fldLogId INTO #DeletedEntityKeys
      FROM dbo.tblLog L
      INNER JOIN dbo.tblJobs J ON J.fldJobId=L.fldJobId
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=J.fldJobId

   SELECT @DeletedLogEntryCount=@DeletedLogEntryCount+@@ROWCOUNT

   UPDATE N SET N.fldJobId=NULL FROM dbo.tblNode N
      INNER JOIN dbo.tblJobs J ON J.fldJobId=N.fldJobId
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=J.fldJobId
      WHERE N.fldRetain=1

   DELETE N 
      FROM tblNodeRelation NR
      INNER JOIN dbo.tblNode N ON N.fldNodeId=NR.fldNodeId OR N.fldNodeId=NR.fldRelNodeId
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=N.fldJobId
      WHERE N.fldRetain=0

   DELETE N 
      OUTPUT 18, DELETED.fldNodeId INTO #DeletedEntityKeys
      FROM dbo.tblNode N 
      INNER JOIN dbo.tblJobs J ON J.fldJobId=N.fldJobId
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=J.fldJobId

   SELECT @DeletedNodeCount=@DeletedNodeCount+@@ROWCOUNT

   UPDATE J SET fldParentJobId=NULL FROM dbo.tblJobs J
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=J.fldJobId

   UPDATE E SET E.fldJobId=NULL FROM dbo.tblEvents E
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=19 AND EK.fldId=E.fldEventId

   DELETE JA 
      OUTPUT DELETED.fldArgumentId INTO #DeletedArgumentRefs
      FROM dbo.tblJobArguments JA
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=JA.fldJobId

   DELETE JOA FROM dbo.tblJobOutArguments JOA
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=JOA.fldJobId

   UPDATE EH SET EH.fldJobId=NULL FROM tblEventHistory EH         
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=EH.fldJobId

   UPDATE JH SET JH.fldCallerJobId=NULL FROM tblJobHistory JH 
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=JH.fldCallerJobId

   UPDATE JH SET JH.fldRelatedJobId=NULL FROM tblJobHistory JH 
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=JH.fldRelatedJobId

   DELETE J 
      OUTPUT 59, DELETED.fldJobId INTO #DeletedEntityKeys         
      FROM dbo.tblJobs J
         INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=59 AND EK.fldId=J.fldJobId

   SELECT @DeletedJobCount=@DeletedJobCount+@@ROWCOUNT

   DELETE EA 
      OUTPUT DELETED.fldArgumentId INTO #DeletedArgumentRefs
      FROM dbo.tblEventArguments EA         
      INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=19 AND EK.fldId=EA.fldEventId

   DELETE E 
      OUTPUT 19, DELETED.fldEventId INTO #DeletedEntityKeys         
      FROM dbo.tblEvents E
         INNER JOIN @EntityKeys EK ON EK.fldEntityTypeId=19 AND EK.fldId=E.fldEventId

   SELECT @DeletedEventCount=@DeletedEventCount+@@ROWCOUNT

   DELETE FROM A 
      FROM dbo.tblArguments A   
      INNER JOIN #DeletedArgumentRefs DA ON DA.fldArgumentId=A.fldArgumentId      
      WHERE A.fldArgumentId NOT IN (SELECT fldArgumentId FROM tblJobArguments)
      AND   A.fldArgumentId NOT IN (SELECT fldArgumentId FROM tblEventArguments)

   DELETE TCC FROM dbo.tblTrackingCleanupCandidates TCC
      INNER JOIN #DeletedEntityKeys DK ON DK.fldEntityTypeId=TCC.fldEntityTypeId AND Dk.fldId=TCC.fldEntityId

   MERGE tblDeletedNodeIds AS TARGET
      USING (SELECT fldId FROM #DeletedEntityKeys WHERE fldEntityTypeId=18) AS SOURCE ON TARGET.fldNodeId=SOURCE.fldId
      WHEN NOT MATCHED BY TARGET THEN 
         INSERT (fldNodeId) VALUES (SOURCE.fldId);

   DROP TABLE #DeletedEntityKeys
   DROP TABLE #DeletedArgumentRefs
END
4

1 回答 1

0

问题有点奇怪,但似乎WITH RECOMPILE以某种方式运行 SP 可以解决问题。起初我尝试使用SET ARITHABORT ONwhich 使查询更快,但是在运行了很多次之后,如果我将其更改为SET ARITHABORT OFF.

重新编译存储过程显然会改变执行计划,这似乎加快了速度。所以我想我必须不时WITH RECOMPILE在我的循环中添加到存储过程的执行中......

文档说, SQL Server 会自动重新编译存储过程并在这样做有利时触发。显然,这并不总是正确的。

于 2013-08-19T12:17:28.223 回答