Почему в запросе SqlClient / SqlConnection (ExecuteScalar или ExecuteReader) есть тайм-аут при использовании .NET Core, но не .NET обычный или SSMS? - PullRequest
0 голосов
/ 09 мая 2018

Я пытаюсь выполнить SP, используя приведенный ниже код в .Net Core

using (DBContext context = new DBContext()){
                {
using (var command = context.Database.GetDbConnection().CreateCommand())
    {

        command.CommandText = "Sp_Name";
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@input", SqlDbType.VarChar ,3) { Value = InputValue });
        command.Parameters.Add(new SqlParameter("@Return_Value", SqlDbType.VarChar, 3) { Value = string.Empty });

        context.Database.OpenConnection();

        var dataReader = command.ExecuteReader();

        if (dataReader.Read())
        {
            var code = dataReader.GetString(dataReader.GetOrdinal(""));
        }
    }}

Запрос отлично работает для некоторых входных параметров, но для некоторых выдает исключение, например:

- Этот сценарий работает нормально в EF Code и SQL

    SP - exec Sp_Name @input = 'PDX',  @Return_Value = ''

    --Result (No Column Name) - '3I9' 

- Этот сценарий не работает в коде EF, но отлично работает в SQL

    SP - exec Sp_Name @input = 'N01',  @Return_Value = ''

    --Result (No Column Name)  - 'WE5'

Сообщение об исключении

System.Data.SqlClient.SqlException (0x80131904): Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at System.Data.SqlClient.SqlDataReader.get_MetaData()
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.ExecuteScalar()
   at Mednax.ReferringPhysician.Data.PdxService.getGPMSCode(String practiceCode) in C:\Work\GIT\ReferringPhysician2\Mednax.ReferringPhysician.WebAPI\Mednax.ReferringPhysician.Data\PdxService.cs:line 971
ClientConnectionId:199f2b1a-cb1b-4752-8632-9f2c54bcefd8
Error Number:-2,State:0,Class:11

Трассировка стека:

   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at System.Data.SqlClient.SqlDataReader.get_MetaData()
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.ExecuteScalar()

SP выглядит следующим образом:

SP -

(
@Input varchar(3),
@Return_Value varchar(3) output
)
AS

SET NOCOUNT ON
SET @Return_Value = NULL

SELECT  TOP 1   @Return_Value = pacl.P_Code
    FROM    TABLEA pacl with (nolock)
    LEFT OUTER JOIN TABLEB rpp with (nolock)
        ON  rpp.Code = pacl.Code
        AND rpp.P_Code = @Input
    WHERE   rpp.P_Code IS NULL
    ORDER BY pacl.P_Code

IF @@Rowcount = 0 SET   @Return_Value = '***'

Select @Return_Value

Подробности внутри сообщения об объекте исключения:

enter image description here

1 Ответ

0 голосов
/ 10 мая 2018

То, что вы видите здесь, выглядит как неверная запись в кэше плана запросов из-за перехвата параметров, что приводит к таймауту из-за катастрофического плана запросов. Проблема с анализом параметров заключается в том, что он генерирует план запроса на основе значения первого параметра , которое он видит, когда не существует никакого плана запроса для операции (который соответствует текущему режиму выполнения). Если у вас есть сильно смещенные данные, сгенерированный план запроса может подойти для одних значений, но катастрофически для других. Например, рассмотрим сценарий, в котором есть 3 строки с одним значением и 3 миллион строк с другим значением. Если вы генерируете план запроса на основе значения «3 строки», он может принимать решения, оптимизированные для этой величины - он будет хорошо работать для 3, 30 и, вероятно, 300 - но для 3 миллионов это может разрушаться. Аналогично в обратном порядке. Здесь, в Stack Overflow, мы называем это «проблемой Jon Skeet»: у Jon (пользователь № 1 на странице пользователей) распределение данных у совершенно нового 1-повторного пользователя совершенно иное, и планы запросов для Jon ужасны для этого 1 -rep пользователь и наоборот.

К счастью, SQL Server имеет подсказку для запроса в этой ситуации: OPTIMIZE FOR / UNKNOWN. Самое простое использование этого - добавить OPTION ( OPTIMIZE FOR UNKNOWN ) к соответствующему запросу; это указывает на то, что он не должен смещать план запроса в огромной степени на основе значений параметров, наблюдаемых при генерации запроса. Вы также можете указать отдельные параметры, если только некоторые из них проблематичны (например, @userId для нас).

Итак, почему это может работать в SSMS (анализатор запросов) и .NET, но не в ядре .NET? Я предполагаю, что проблема здесь в других SET вариантах. различные SET опции определяют режим выполнения; некоторые из этих опций могут повлиять на генерацию запросов, поэтому может потребоваться отдельный план для двух клиентов с разными опциями SET. Это означает, что .NET Core может эффективно поражать другой кэш плана запросов для .NET, поэтому: когда один работает, другой отказывает. Но: это не значит, что кто-то «хуже»; скорее это просто означает, что один из них случайно сгенерировал план запроса данных, который вызвал катастрофический план . Та же проблема могла возникнуть и в случайное время, когда кэш плана по какой-то причине стал недействительным (как правило, просто: постепенный сдвиг данных) - так же, как самый неуклюжий пользователь (и т. Д.) Использовал сайт. Проблемы с прослушиванием параметров обычно не обнаруживаются сразу - они возникают среди ночи, через 4 дня после того, как кто-либо что-либо развернул.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...