应用程序中简单的插入会超时,但在 SSMS 中速度很快

问题描述 投票:0回答:1

在我的应用程序中,我有一个简单的插入,当使用探查器捕获时,它看起来像这样

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)
,但没有帮助

执行计划:
所以我从SSMS
得到了执行计划 并从活动监视器

捕获缓慢的应用程序计划

我自己没有知识来比较这些计划并看到问题,所以我希望这里有人可以比较它们并帮助我指出问题。

交易就这样完成了

 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,Action

1 wrapCloseInAction)    at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action
1wrapCloseInAction) 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 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method)    at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource
1 完成、布尔值 sendToPipe、Int32 超时、布尔值 asyncWrite、 字符串方法名称)位于 System.Data.SqlClient.SqlCommand.ExecuteNonQuery() 在 Dapper.SqlMapper.ExecuteCommand(IDbConnection cnn, CommandDefinition& 命令,Action
2 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 commandTimeout,Nullable
1 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 dateInActual)中 C:\Development\Git\WebServices\WebServiceMobile\Repositories\Compound\Ford\RepositoryFordFlow.cs:line 29

内部异常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
sql-server sql-execution-plan sql-server-2019
1个回答
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;
             }
         }
     }

}

我现在更改了我的方法,以便它检查它是否在现有连接/事务中,然后使用它,并且仅在情况不存在时才创建一个新连接/事务。

© www.soinside.com 2019 - 2024. All rights reserved.