ADO.Net SQLCommand.ExecuteReader() замедляется или зависает

Среда:

Приложение (написанное на C# для.Net 4) имеет до 10 потоков, каждый поток работает в своем собственном домене приложений. Каждый поток использует ADO.Net DataReader, который получает результаты из хранимой процедуры в SQL-Server 2008. Также поток может использовать ADO.Net для выполнения операции записи (групповая вставка). Все работает на локальной машине.

Проблема № 1:

Иногда (примерно каждый 30-й запуск) выполнение потока резко замедляется. Это происходит, когда DataReader получает результаты хранимой процедуры - SqlCommand.ExecuteReader(). Обычно операция чтения выполняется за 10 секунд. Когда он замедляется, он выполняется через 10-20 минут. SQLProfiler показывает, что данные запрашиваются, хотя и очень медленно.

Callstack замедления (обратите внимание, что нет никаких исключений):

at SNIReadSync(SNI_Conn* , SNI_Packet** , Int32 )
   at SNINativeMethodWrapper.SNIReadSync(SafeHandle pConn, IntPtr& packet, Int32 timeout)
   at System.Data.SqlClient.TdsParserStateObject.ReadSni(DbAsyncResult asyncResult, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParserStateObject.ReadNetworkPacket()
   at System.Data.SqlClient.TdsParserStateObject.ReadBuffer()
   at System.Data.SqlClient.TdsParserStateObject.ReadByteArray(Byte[] buff, Int32 offset, Int32 len)
   at System.Data.SqlClient.TdsParserStateObject.ReadString(Int32 length)
   at System.Data.SqlClient.TdsParser.ReadSqlStringValue(SqlBuffer value, Byte type, Int32 length, Encoding encoding, Boolean isPlp, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.ReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, Int32 length, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlDataReader.ReadColumnData()
   at System.Data.SqlClient.SqlDataReader.ReadColumnHeader(Int32 i)
   at System.Data.SqlClient.SqlDataReader.ReadColumn(Int32 i, Boolean setTimeout)
   at System.Data.SqlClient.SqlDataReader.GetValueInternal(Int32 i)
   at System.Data.SqlClient.SqlDataReader.GetValue(Int32 i)
   at System.Data.SqlClient.SqlDataReader.get_Item(String name)
   at ****.Core.TableDataImporter.ImportDataFromExcel(Int32 tableId, ExcelEntityLocation location, Boolean& updateResult) in …

Проблема № 2:

Вместо замедления может висеть нить.

Стек вызовов:

at SNIReadSync(SNI_Conn* , SNI_Packet** , Int32 )
   at SNINativeMethodWrapper.SNIReadSync(SafeHandle pConn, IntPtr& packet, Int32 timeout)
   at System.Data.SqlClient.TdsParserStateObject.ReadSni(DbAsyncResult asyncResult, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParserStateObject.ReadNetworkPacket()
   at System.Data.SqlClient.TdsParserStateObject.ReadBuffer()
   at System.Data.SqlClient.TdsParserStateObject.ReadByte()
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
   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)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader()

Callstacks были получены с использованием инструментов отладки в фоновом потоке. Никаких исключений не бывает, замедление или зависание.

SNIReadSync - это механизм, который работает на сетевом уровне и работает с передачей пакетов по сети. Мы воспроизвели эту проблему на локальном компьютере, удалив сетевые проблемы из уравнения.

Мы ищем какие-либо входные данные и решения или обходные пути для этого замедления / зависания. На данный момент мы планируем обнаружить замедление и повторить операцию. Заранее спасибо.

Я добавляю упрощенный код для метода в соответствии с просьбой:

  public void ImportDataFromExcel()
    {            
        try
        {                
            var _сonnectionBuilk = ... ; // singleton connection (at the app level)
            var spName = ... ; // stored procedure name

        var сonnectionToRead = new SqlConnection(connectionStirng);
        сonnectionToRead.Open();

        var sqlCommand = new SqlCommand(spName);
        sqlCommand.CommandType = CommandType.StoredProcedure; 
        sqlCommand.Parameters.Add(param1Name, SqlDbType.Int).Value = ...;
        sqlCommand.Parameters.Add(param2Name, SqlDbType.Int).Value = ...;
        sqlCommand.Parameters.Add(param2Name, SqlDbType.Int).Value = ...;

        sqlCommand.Connection = сonnectionToRead;            
        sqlCommand.CommandTimeout = timeout; // 120 sec

        using (var dataReader = sqlCommand.ExecuteReader())
        {
                dataReader.Read();
            .....
            int pos1 = dataReader.GetOrdinal(columnName1);
            int pos2 = dataReader.GetOrdinal(columnName2);
            int pos3 = dataReader.GetOrdinal(columnName3);
            int pos4 = dataReader.GetOrdinal(columnName4);
                .....                    

            // reading data from sqldatareader
            int val1 = dataReader.GetInt32(pos1);
            int val2 = dataReader.GetInt32(pos2);
            int val3 = dataReader.GetInt32(pos3);
            var val4 = dataReader.GetDateTime(pos4);
            .....

            // append read data into bulkTable
            bulkTable.AddCellValue(val1, val2, val3, val4);  // bulkTable wraps DataTable, and appends DataRow inside. 

            if(bulkTable.DataTable.Rows > MaxRowsCount)
            {
                using (var bulkCopy = new SqlBulkCopy(_сonnectionBuilk))
                {
                    bulkCopy.DestinationTableName = _fullTableName;
                    bulkCopy.WriteToServer(bulkTable.DataTable);
                }

                var sqlCommandTransfer = new SqlCommand(spName);
                sqlCommandTransfer.CommandType = CommandType.StoredProcedure; 
                sqlCommandTransfer.Parameters.Add(param1Name, SqlDbType.Int).Value = ...;
                sqlCommandTransfer.Connection = _сonnectionBuilk;
                ....
                sqlCommandTransfer.ExecuteNonQuery(); // transfering data from temp bulk table into original table
            }
        }
    }
    finally
    {
        bulkTable.Dispose();
        сonnectionToRead.Close();
    }
}

4 ответа

Мы пытались отладить похожую проблему в течение нескольких месяцев и, наконец, нашли ее сегодня...

У нас был запрос, который был спрятан в кеш (без вызова ToList/ToArray/ и т. Д.). Запрос был фактически привязан к соединению, которое с тех пор было очищено, и мы получили то, что, по-видимому, было на 100% заблокировано ЦП из [code]ReadSni[/code] (полный стек включен ниже).

Я подозреваю, что кеширующий код был написан до того, как запрос был изменен на использование Linq (и вместо этого он возвращал List, но все еще приводился как IEumberable), поэтому он был введен, когда кто-то сделал доступ к данным "ленивым".

Я не могу объяснить, почему это происходило только каждые несколько дней в производстве; либо кеширование не используется часто, либо соединение должно было находиться в определенном состоянии, чтобы оно таким образом не работало.

[code] Идентификатор потока ОС: 0x20b8 (27) Дочерний сайт IP-вызова дочернего SP 16edd0fc 6184267e System.Data.SqlClient.TdsParserStateObject.ReadSni(System.Data.Common.DbAsyncResult, System.Data.SqlClient.TdsParserS261D1D13434D4D1DB).SqlClient.TdsParserStateObject.ReadNetworkPacket() 16edd144 618446af System.Data.SqlClient.TdsParserStateObject.ReadBuffer() 16edd150 61c583d0 System.Data.SqlClient.TdsParserStateObject.CleanWire() 16edd15c 61d1beb9 System.Data.SqlClient.TdsParser.Deactivate(Boolean) 16edd174 6184995f System.Data.SqlClient.SqlInternalConnectionTds.InternalDeactivate() 16edd180 61849640 System.Data.SqlClient.SqlInternalConnection.Deactivate() 16edd1b0 61849587 System.Data. Data.ProviderBase.DbConnectionInternal) 16edd224 61849384 System.Data.ProviderBase.DbConnectionPool.PutObject(System.Data.ProviderBase.DbConnectionInternal, System.Object) 16edd26c 6184920c System.Data.ProviderBase.DbConnectionInternal.CloseConnection(System.Data.Common.DbConnection, System.Data.ProviderBase.DbConnectionFactory) 16edd2ac 618490f7 System.Data.SqlClient.SqlConnect.Connect System.Data.ProviderBase.DbConnectionFactory) 16edd2c4 618393bf System.Data.SqlClient.SqlConnection.Close() 16edd304 11238f0a..IDbConnection) 16edd34c 11aceb42 NHibernate.AdoNet.ConnectionManager.CloseConnection() 16edd358 11aceb02 NHibernate.AdoNet.ConnectionManager.AggressiveRelease() 16edd364 11acf783 NHibernate. ITransaction) 16edd3ec 11acf5de NHibernate.AdoNet.ConnectionManager.AfterNonTransactionalQuery(Boolean) 16edd3fc 11acf539 NHibernate.Impl.AbstractSessionImpl.AfterOperation(логическое значение) 16edd474 130311e4 NHibernate.Impl.SessionImpl.List(NHibernate.IQueryExpression, NHibernate.Engine.Impl NHibernate.IQueryExpression, NHibernate.Engine.QueryParameters) 16ede538 13030b68 NHibernate..Linq.DefaultQueryProvider.Execute (System.Linq.Expressions.Expression) 16ede5b0 11d4c108 NHibernate.Linq.DefaultQueryProvider.Execute [System._ Canon, mscorlib] 16ede5c4 11d4c0a6 Remotion.LinqQ 1[[System.__Canon, mscorlib]].GetEnumerator() 16ede5d4 61022108 System.Linq.Enumerable+WhereEnumerableIterator 1 [[System. _Canon, mscorlib]]. MoveNext () * ПРЕДУПРЕЖДЕНИЕ: невозможно проверить контрольную сумму для System.Core.ni.dll * ОШИБКА: загрузка модуля завершена, но символы не могут быть загружены для System.Core.ni.dll

16ede5e4 610166ea System.Linq.Buffer 1[[System.__Canon, mscorlib]]..ctor(System.Collections.Generic.IEnumerable 1) 16ede620 6122e171 System.Linq.OrderedEnumerable 1+<GetEnumerator>d__0[[System.__Canon, mscorlib]].MoveNext() 16ede63c 79b39758 System.Collections.Generic.List 1 [[System.__ Canon, mscorlib]].. ctor (System.Collections.Generic.IEnumerable`1) * ПРЕДУПРЕЖДЕНИЕ: невозможно проверить контрольную сумму для mscorlib.ni.dll * ОШИБКА: загрузка модуля завершена, но символы не могут быть загружены для mscorlib.ni.dll

16ede66c 61021acf System.Linq.Enumerable.ToList "> [Система.__Canon, mscorlib] [/code]

Поскольку код работает некоторое время, мы можем сузить его до:

  • Блокировка / блокировка базы данных от ваших процессов и некоторых других.
  • Блокировка / блокировка базы данных только от ваших процессов.
  • Сетевое подключение.
  • Состояние данных.
  • Дисковое пространство или другая, казалось бы, не связанная проблема на сервере.

Я бы сказал, что это проблема блокировки / блокировки базы данных. Но вам действительно нужно определить это наверняка. Сделать это:

  • Убедитесь, что на диске есть место для записи в базы данных, включая журналы базы данных и любые другие журналы.
  • Убедитесь, что никакие другие процессы не используют базу данных.
  • Желательно также использовать локальную базу данных для устранения проблем с сетью.
  • Вы используете.Net 4 - поэтому, если вы используете Задачи, тогда будет очень легко заставить их работать синхронно с перегрузками. Сделайте это и посмотрите, не исчезла ли проблема.

Выполнение всего вышеперечисленного должно устранить проблемы - и вы можете начать сужать его дальше оттуда.

У меня была та же проблема, и я решил после нескольких тестов, что я говорю о моем конкретном случае, но я думаю, что это может быть полезно.

В моей системе у меня есть сложный движок OLAP на SQLServer 2008. За эти годы объем операций увеличился в объеме и количестве, и случайно я получил сообщение об ошибке, упомянутое в посте... при увеличении операций ошибка исчезла. Я дважды проверил код всех операций OLAP и все транзакции, соединения и объекты чтения были обработаны отлично.

Критические точки, которые генерировали ошибки (но не всегда), когда я был разделами циклов WHILE для запроса SQLServer, повторные бомбардировки запросов генерировали ошибки в БД

Ho ristrutturato le molte piccole запрашивает una query pi in grandi(в конце набора результатов) ed ho operato delle operazioni Attraverso LINQ, il risultato è stato soprendente. Perdormance di 10 volte migliorate e 0 errori.

Совет, который я могу дать, заключается в том, чтобы хорошо анализировать критические участки кода, даже если вы будете очень осторожны в использовании соединений, DataReader и транзакций.

Я знаю, что этому вопросу 8 лет, но, возможно, мой ответ поможет какому-нибудь бедному разработчику, который столкнется с этой проблемой в будущем :)

В моем случае это был тупик в базе данных, но не совсем очевидный. Когда была выполнена процедура sp_whoisactive, она вернула много строк, заблокированных одним спящим соединением. Сначала я подумал, что это может быть из-за каких-то проблем с сетью, но после нескольких часов тестов с windbg и отключения сетевого кабеля я все еще не смог воспроизвести зависание.

В конце концов я заметил, что на самом деле было 2 соединения из приложения от заблокированного пользователя:

  • подвесной, который уже держит несколько замков
  • другой, который был заблокирован подключением от другого пользователя.

Оказалось, что:

  1. Первое соединение было открыто, оно заблокировало некоторые объекты в базе данных, но в итоге получило исключение через команду RAISERROR
  2. Тем временем появляется соединение из другого приложения, принимает некоторые блокировки на базе данных, но в конце концов блокируется первым
  3. Приложение, которое уже установило соединение №1, открыло новое с помощью опции транзакции RequiresNew, которая затем была заблокирована вторым соединением.

Также все 3 подключения были выполнены в временном окне 7 мс, что объясняет, почему такой проблемы не было в течение нескольких месяцев.

Если вы столкнулись с этой проблемой, я советую использовать процедуру sp_whoisactive - http://whoisactive.com Вы можете выполнить ее с параметром @get_locks = 1, который вернет все заблокированные объекты по данному соединению. Это в сочетании со стеком вызовов должно дать вам достаточно информации, чтобы решить эту проблему. Или определить, не так ли;)

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