我有一个.NET 4.5程序,它基本上运行一个循环,在该循环中,使用相同的SqlConnection
多次执行相同的存储过程。如果发生命令超时,它会递增并重新运行查询(这是出于测试目的,在实际应用程序中,将修改SP的输入参数)。
可以在下面找到有问题的存储过程。基本上它是SELECT/DELETE/UPDATE
语句的混合。它以SET XACT_ABORT ON
开头,不包含BEGIN TRAN
或ROLLBACK 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
答案 0 :(得分:0)
问题有点奇怪,但似乎用WITH RECOMPILE
运行SP会以某种方式解决问题。起初我尝试使用SET ARITHABORT ON
使查询更快,但在运行它之后,如果我将其更改为SET ARITHABORT OFF
,它会突然变得更快。
重新编译存储过程显然会改变执行计划,这似乎加快了速度。所以我想我必须每隔一段时间在我的循环中将WITH RECOMPILE
添加到存储过程的执行中......
文档说明了这一点 SQL Server会在执行此操作时自动重新编译存储过程和触发器。显然,这并非总是如此。