我有一个 .NET 4.5 程序,它基本上运行一个循环,其中多次执行相同的存储过程,使用相同的SqlConnection
. 如果发生命令超时,它会增加并重新运行查询(这是出于测试目的,在实际应用程序中,将修改 SP 的输入参数)。
有问题的存储过程可以在下面找到。基本上它是各种SELECT/DELETE/UPDATE
陈述。它以 no or 语句开头SET XACT_ABORT ON
并且不包含BEGIN TRAN
orROLLBACK 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 的连接)
编辑:
我现在也尝试了所有可能使用相关数据库的KILL
SPID 。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