在我的应用程序中,我有一个简单的插入,当使用探查器捕获时,它看起来像这样
insert into ford.tblFordCompoundFlowVehicle
(FordCompoundFlowID, CompoundVehicleID, SortOrder, Status1ToSend, Status2ToSend, FordFlowTriggerID, SendTriggerSatisfied, DateSend, FordCompoundFlowDefinitionID)
select fcfd.FordCompoundFlowID,
9711,
fcfdd.SortOrder,
fcfdd.Status1ToSend,
fcfdd.Status2ToSend,
fcfdd.FordFlowTriggerID,
0,
null,
2
from ford.tblFordCompoundFlowDefinitionDetail fcfdd
inner join ford.tblFordCompoundFlowDefinition fcfd on fcfdd.FordCompoundFlowDefinitionID = fcfd.FordCompoundFlowDefinitionID
where fcfdd.FordCompoundFlowDefinitionID = 2
order by fcfdd.SortOrder
这是使用 Dapper 发送到数据库的,如下所示
string sql =
$"""
insert into ford.tblFordCompoundFlowVehicle
(FordCompoundFlowID, CompoundVehicleID, SortOrder, Status1ToSend, Status2ToSend, FordFlowTriggerID, SendTriggerSatisfied, DateSend, FordCompoundFlowDefinitionID)
select fcfd.FordCompoundFlowID,
{compoundVehicleID},
fcfdd.SortOrder,
fcfdd.Status1ToSend,
fcfdd.Status2ToSend,
fcfdd.FordFlowTriggerID,
0,
null,
{fordCompoundFlowDefinitionID}
from ford.tblFordCompoundFlowDefinitionDetail fcfdd
inner join ford.tblFordCompoundFlowDefinition fcfd on fcfdd.FordCompoundFlowDefinitionID = fcfd.FordCompoundFlowDefinitionID
where fcfdd.FordCompoundFlowDefinitionID = {fordCompoundFlowDefinitionID.Value}
order by fcfdd.SortOrder
OPTION(RECOMPILE)
""";
connection.Execute(sql, null, transaction);
我没有使用
SqlCommand
来让代码更短一点,而且这些值都是 int
所以 SQL 注入无论如何都会很困难。
现在的问题是,当这个查询运行时,它需要很长时间才能超时,但是当我在探查器中捕获查询并在 SSMS 中运行它时,速度非常快。
我也尝试过应用程序中的选项
OPTION(RECOMPILE)
,但没有帮助
捕获缓慢的应用程序计划
我自己没有知识来比较这些计划并看到问题,所以我希望这里有人可以比较它们并帮助我指出问题。
交易就这样完成了
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlTransaction transaction = connection.BeginTransaction())
{
try
{
fordCompoundFlowDefinitionID = CreateFordCompoundFlowVehicle(connection, transaction, compoundVehicleID);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
private int? CreateFordCompoundFlowVehicle(SqlConnection connection, SqlTransaction transaction, int compoundVehicleID)
{
int? result = null;
// check if there is any importdata for this compoundvehicleid, if not than it stops right here
int? fordCompoundFlowDefinitionID = GetFordCompoundFlowDefinitionID(connection, transaction, compoundVehicleID);
if (fordCompoundFlowDefinitionID.HasValue)
{
string sql =
$"""
insert into ford.tblFordCompoundFlowVehicle
(FordCompoundFlowID, CompoundVehicleID, SortOrder, Status1ToSend, Status2ToSend, FordFlowTriggerID, SendTriggerSatisfied, DateSend, FordCompoundFlowDefinitionID)
select fcfd.FordCompoundFlowID,
{compoundVehicleID},
fcfdd.SortOrder,
fcfdd.Status1ToSend,
fcfdd.Status2ToSend,
fcfdd.FordFlowTriggerID,
0,
null,
{fordCompoundFlowDefinitionID}
from ford.tblFordCompoundFlowDefinitionDetail fcfdd
inner join ford.tblFordCompoundFlowDefinition fcfd on fcfdd.FordCompoundFlowDefinitionID = fcfd.FordCompoundFlowDefinitionID
where fcfdd.FordCompoundFlowDefinitionID = {fordCompoundFlowDefinitionID.Value}
order by fcfdd.SortOrder
OPTION(RECOMPILE)
""";
-- THIS TIMES OUT
connection.Execute(sql, null, transaction);
result = fordCompoundFlowDefinitionID;
}
return result;
}
我按照建议将代码更改为参数化查询,但得到相同的结果
string sql =
"""
insert into ford.tblFordCompoundFlowVehicle
(FordCompoundFlowID, CompoundVehicleID, SortOrder, Status1ToSend, Status2ToSend, FordFlowTriggerID, SendTriggerSatisfied, DateSend, FordCompoundFlowDefinitionID)
select fcfd.FordCompoundFlowID,
@CompoundVehicleID,
fcfdd.SortOrder,
fcfdd.Status1ToSend,
fcfdd.Status2ToSend,
fcfdd.FordFlowTriggerID,
0,
null,
@FordCompoundFlowDefinitionID
from ford.tblFordCompoundFlowDefinitionDetail fcfdd
inner join ford.tblFordCompoundFlowDefinition fcfd on fcfdd.FordCompoundFlowDefinitionID = fcfd.FordCompoundFlowDefinitionID
where fcfdd.FordCompoundFlowDefinitionID = @FordCompoundFlowDefinitionID
order by fcfdd.SortOrder
OPTION(RECOMPILE)
""";
var parameters = new {CompoundVehicleID = compoundVehicleID, FordCompoundFlowDefinitionID = fordCompoundFlowDefinitionID };
connection.Execute(sql, parameters, transaction);
分析器中捕获的语句
exec sp_executesql N'insert into ford.tblFordCompoundFlowVehicle
(FordCompoundFlowID, CompoundVehicleID, SortOrder, Status1ToSend, Status2ToSend, FordFlowTriggerID, SendTriggerSatisfied, DateSend, FordCompoundFlowDefinitionID)
select fcfd.FordCompoundFlowID,
@CompoundVehicleID,
fcfdd.SortOrder,
fcfdd.Status1ToSend,
fcfdd.Status2ToSend,
fcfdd.FordFlowTriggerID,
0,
null,
@FordCompoundFlowDefinitionID
from ford.tblFordCompoundFlowDefinitionDetail fcfdd
inner join ford.tblFordCompoundFlowDefinition fcfd on fcfdd.FordCompoundFlowDefinitionID = fcfd.FordCompoundFlowDefinitionID
where fcfdd.FordCompoundFlowDefinitionID = @FordCompoundFlowDefinitionID
order by fcfdd.SortOrder
OPTION(RECOMPILE)',N'@CompoundVehicleID int,@FordCompoundFlowDefinitionID int',@CompoundVehicleID=9711,@FordCompoundFlowDefinitionID=2
go
System.Data.SqlClient.SqlException HResult=0x80131904
消息=超时已过。 超时时间已过 操作完成或服务器没有响应。手术 被用户取消。 来源=Core .Net SqlClient 数据提供程序
堆栈跟踪:位于 System.Data.SqlClient.SqlConnection.OnError(SqlException异常, 布尔值breakConnection,Action1wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj、布尔值 callerHasConnectionLock、布尔值 asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler、SqlDataReader 数据流、 BulkCopySimpleResultSet BulkCopyHandler、TdsParserStateObject stateObj、布尔值和 dataReady)位于 System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior,字符串resetOptionsString) at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior、RunBehavior runBehavior、布尔值 returnStream、布尔值 异步、Int32 超时、任务和任务、布尔 asyncWrite、SqlDataReader ds) 在 System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior、RunBehavior runBehavior、布尔 returnStream、 任务完成源1 wrapCloseInAction) at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action
1 完成、布尔值 sendToPipe、Int32 超时、布尔值 asyncWrite、 字符串方法名称)位于 System.Data.SqlClient.SqlCommand.ExecuteNonQuery() 在 Dapper.SqlMapper.ExecuteCommand(IDbConnection cnn, CommandDefinition& 命令,Action1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource
1 commandTimeout,Nullable2 paramReader) in /_/Dapper/SqlMapper.cs:line 2965 at Dapper.SqlMapper.ExecuteImpl(IDbConnection cnn, CommandDefinition& command) in /_/Dapper/SqlMapper.cs:line 656 at Dapper.SqlMapper.Execute(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Nullable
1 dateInActual)中 C:\Development\Git\WebServices\WebServiceMobile\Repositories\Compound\Ford\RepositoryFordFlow.cs:line 291 commandType) in /_/Dapper/SqlMapper.cs:line 527 at WebServiceMobile.Repositories.Compound.RepositoryFordFlow.CreateFordCompoundFlowVehicle(SqlConnection connection, SqlTransaction transaction, Int32 compoundVehicleID) in C:\Development\Git\WebServices\WebServiceMobile\Repositories\Compound\Ford\RepositoryFordFlow.cs:line 124 at WebServiceMobile.Repositories.Compound.RepositoryFordFlow.HandleIncomingVehicle(Int32 compoundVehicleID, Nullable
内部异常1:Win32Exception:等待操作超时。
我尝试不使用交易,结果相同
System.Data.SqlClient.SqlException:'超时已过期。 超时 操作完成之前经过的时间或服务器已关闭 未响应。用户取消了操作。'
从应用程序中运行的查询捕获的新计划
计划:
https://www.brentozar.com/pastetheplan/?id=B1vTWaMIR
实时统计:
https://www.brentozar.com/pastetheplan/?id=H1-8MpGUA
运行 sp_who2
按照建议,我在慢速查询运行时运行了 exec
sp_who2
,这就是结果
SPID | 状态 | 登录 | 主机名 | 街区 | 数据库名称 | 命令 | CPU时间 | 磁盘IO | 最后一批 | 程序名称 | SPID | 请求ID |
---|---|---|---|---|---|---|---|---|---|---|---|---|
87 | 暂停 | 手掌测试 | XXXX | 77 | xxxx | 插入 | 0 | 2 | 06/21 11:46:38 | gttWeb服务 | 87 | 0 |
我发现了问题,似乎从应用程序运行时有一个锁使我的查询处于挂起状态。
我发现这一点要感谢 @sa-es-ir 的评论,它让我在慢速查询运行时运行
sp_who2
。然后我注意到我运行查询的同一个应用程序有一个块,但使用另一个 spid。
所以查询实际上与问题没有太大关系,我猜执行计划应该退出类似,但由于查询一直在等待,客户端厌倦了等待并调用了超时。
这是怎么发生的?
似乎当您在 C# 中的连接开始一个事务,并且您创建一个开始新事务的新连接时,任何需要在第一个事务中插入/更新的行的查询都需要等待该事务结束。
基本上我有这样的事情
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlTransaction transaction = connection.BeginTransaction())
{
try
{
SomeProcedure();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
public void SomeProcedure()
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlTransaction transaction = connection.BeginTransaction())
{
try
{
// HERE WAS MY QUERY THAT TIMED OUT
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
}
我现在更改了我的方法,以便它检查它是否在现有连接/事务中,然后使用它,并且仅在情况不存在时才创建一个新连接/事务。