即使程序重新启动,.NET的T-SQL查询也会在一段时间后开始非常缓慢地运行

时间:2013-08-18 09:54:01

标签: .net sql sql-server-2012 .net-4.5

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

可以在下面找到有问题的存储过程。基本上它是SELECT/DELETE/UPDATE语句的混合。它以SET XACT_ABORT ON开头,不包含BEGIN TRANROLLBACK TRAN语句。

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

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

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

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

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

究竟是什么导致这种行为?我想不出为什么SQL Server会以这种方式开始行为的原因。如果SP更新,为什么查询突然开始运行得更快? (为什么它会首先放慢速度,但只能来自.NET的连接)

修改 我现在还尝试KILL sys.sysprocesses使用相关数据库的所有可能的SPID。有许多系统进程无法被杀死,但这并没有解决问题。 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

1 个答案:

答案 0 :(得分:0)

问题有点奇怪,但似乎用WITH RECOMPILE运行SP会以某种方式解决问题。起初我尝试使用SET ARITHABORT ON使查询更快,但在运行它之后,如果我将其更改为SET ARITHABORT OFF,它会突然变得更快。

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

文档说明了这一点 SQL Server会在执行此操作时自动重新编译存储过程和触发器。显然,这并非总是如此。