Простое время вставки в приложении, но быстрое в SSMS

В моем приложении есть простая вставка, которая при захвате с помощью профилировщика выглядит так

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
StackTrace: в System.Data.SqlClient.SqlConnection.OnError (исключение SqlException, Логическое значение BreakConnection, Action1 wrapCloseInAction) at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action1 оберткаCloseInAction) на System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, логическое значение callerHasConnectionLock, логическое значение asyncClose) в System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, поток данных SqlDataReader, BulkCopySimpleResultSet BulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) в System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, строка сбросаOptionsString) в System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean асинхронный, тайм-аут Int32, задача и задача, логическое значение asyncWrite, SqlDataReader дс) в System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource1 завершение, логическое значение sendToPipe, тайм-аут Int32, логическое значение asyncWrite, Строковое имя метода) в System.Data.SqlClient.SqlCommand.ExecuteNonQuery() в Dapper.SqlMapper.ExecuteCommand(IDbConnection cnn, CommandDefinition& команда, Action2 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, Nullable1 командаTimeout, Nullable1 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, Nullable1 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 во время выполнения медленного запроса, вот результат

СПИД Положение дел Авторизоваться Имя хоста BlkBy Имя БД Команда CPUTime ДискИО Последняя партия Имя программы СПИД ЗАПРОСИД 87 ПРИОСТАНОВЛЕННЫЙ ПалмТест ХХХХ 77 хххх ВСТАВЛЯТЬ 0 2 21.06 11:46:38 gttWebService 87 0

Как вы определили transaction и совершаете ли вы это?

sa-es-ir 21.06.2024 09:59

@sa-es-ir Я отредактировал вопрос и включил эту информацию

GuidoG 21.06.2024 10:06

«Я не использовал SqlCommand, чтобы сделать этот код немного короче, кроме того, все значения являются целыми, поэтому внедрение sql в любом случае будет затруднено...» - это все равно означает, что вы столкнетесь с проблемами с принудительной параметризацией.

Dai 21.06.2024 10:07

@Dai Я верну SqlCommand, как только время ожидания будет исправлено, пожалуйста, давайте сосредоточимся на реальной проблеме

GuidoG 21.06.2024 10:07

обязательно: sommarskog.se/query-plan-mysteries.html

Mitch Wheat 21.06.2024 10:13

@sa-es-ir Я не успеваю выполнить коммит из-за тайм-аута. Вот почему я добавил оба плана выполнения, чтобы вы могли помочь мне выяснить, почему у меня истекает время.

GuidoG 21.06.2024 10:14

@MitchWheat Да, я читал здесь, но все сводится к изучению планов выполнения, о которых у меня недостаточно знаний

GuidoG 21.06.2024 10:15

@GuidoG Моя точка зрения актуальна, потому что принудительная параметризация может привести к неправильному выбору плана запроса, что, в свою очередь, приводит к замедлению выполнения запроса/оператора.

Dai 21.06.2024 10:16

@Dai Хорошо, когда вы сравните оба плана, вы увидите, что это произошло тогда? Я не могу разобраться в планах

GuidoG 21.06.2024 10:17

Можете ли вы также поделиться исключением? Это запрос или время ожидания соединения? также, если возможно, попробуйте проверить SQL Profiler, чтобы узнать, какое именно приложение пытается запустить на SQL-сервере.

sa-es-ir 21.06.2024 10:25

@sa-es-ir Я уже показываю в вопросе то, что я захватил из профилировщика

GuidoG 21.06.2024 10:26

@sa-es-ir Теперь включено точное сообщение об ошибке

GuidoG 21.06.2024 10:38

@Dai Я параметризовал запрос (см. редактирование в вопросе), но результат тот же

GuidoG 21.06.2024 10:38

@GuidoG Да, извини, я только что прочитал это еще раз. Я думаю, что проблема не в плане запроса, а в транзакции, и, возможно, таблица заблокирована, вы также можете проверить сообщение об исключении в профилировщике SQL: nimblegecko.com/…

sa-es-ir 21.06.2024 10:40

@sa-es-ir Я не использую профилировщик из ssms, а использую экспресс-профилировщик, я не вижу там никаких исключений

GuidoG 21.06.2024 10:40

@sa-es-ir Если стол заблокирован, должно ли это быть не видно в планах?

GuidoG 21.06.2024 10:46

@sa-es-ir Я пробовал без транзакции, результат тот же

GuidoG 21.06.2024 10:50

Оба вставленных плана имеют SET ARITHABORT ON, что предполагает, что они оба были созданы в результате выполнения запросов в SSMS. Как выглядит «плохой» план Дэппера? то есть: он почти наверняка будет иметь другой набор @@OPTIONS, который может влиять на генерацию, выполнение и поведение плана.

AlwaysLearning 21.06.2024 10:53

@AlwaysLearning Извините, но медленный план, который я получил из монитора активности в SSMS, недавних медленных запросов, плана отображения правой кнопкой мыши, а затем сохранил его. Так это должен быть план из приложения или я что-то упускаю?

GuidoG 21.06.2024 10:56

Быстрое и грязное решение для такого рода проблем: обновить статистику (с полным сканированием) для некоторой базовой таблицы (при этом обновляется статистика И уничтожается кэшированные планы), сразу же после этого запускается запрос приложения - это часто (но не всегда) создает и сохраняет лучший план запроса. Примечание. Тогда не используйте with(recompile).

Arvo 21.06.2024 10:57

В качестве быстрой проверки вы можете попробовать добавить к запросам SET ARITHABORT OFF; перед их выполнением в SSMS, чтобы проверить, выполняются ли они медленно. Включение параметра «Показать фактический план выполнения» на панели инструментов должно выявить различия.

AlwaysLearning 21.06.2024 11:01

@AlwaysLearning Я пытаюсь снова захватить план из приложения, дайте мне минутку

GuidoG 21.06.2024 11:03

@AlwaysLearning ОК, я записал парамитризованный план запроса при повторном запуске из приложения, посмотрите последние изменения в вопросе, я поместил туда план и действующую статистику, надеюсь, это поможет вам или кому-то другому, чтобы помочь мне

GuidoG 21.06.2024 11:06

@AlwaysLearning Если SET ARITHABORT OFF в ssms, он по-прежнему остается там быстрым

GuidoG 21.06.2024 11:08

@GuidoG, пока выполняется запрос, можешь ли ты запустить EXEC sp_who2 и проверить столбцы status и blkBy?

sa-es-ir 21.06.2024 11:21

@Арво, я попробовал это, но результат тот же

GuidoG 21.06.2024 11:45

@sa-es-ir ОК, я сделал это, я поместил результат в последнюю редакцию вопроса

GuidoG 21.06.2024 11:50

@GuidoG Теперь это имеет смысл! вам нужно проверить Blk By, который показывает, что другой SPID блокирует запрос, и попытаться найти этот запрос в той же таблице.

sa-es-ir 21.06.2024 11:54

@sa-es-ir Да, наконец-то мы добились некоторого прогресса, большое спасибо, что показали мне это

GuidoG 21.06.2024 11:56

@GuidoG С нетерпением жду возможности увидеть реальную проблему :)

sa-es-ir 21.06.2024 12:02

@sa-es-ir Я опубликовал ответ, в котором объясняется, что произошло и что я сделал не так

GuidoG 21.06.2024 13:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
31
194
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел проблему, кажется, при запуске из приложения произошла блокировка, которая переводила мой запрос в приостановленное состояние.
Я обнаружил это благодаря комментарию @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;
             }
         }
     }

}

Теперь я изменил свой метод, чтобы он проверял, находится ли он в существующем соединении/транзакции, а затем использовал его и создавал новый только в том случае, если это не так.

Другие вопросы по теме