簡體   English   中英

ADO.Net SQLCommand.ExecuteReader() 變慢或掛起

[英]ADO.Net SQLCommand.ExecuteReader() slows down or hangs

環境:

應用程序(為 .Net 4 用 C# 編寫)最多有 10 個線程,每個線程在自己的 AppDomain 中運行。 每個線程使用 ADO.Net DataReader 從 SQL-Server 2008 上的存儲過程中獲取結果。此外,線程可以使用 ADO.Net 執行寫入操作(批量插入)。 一切都在本地機器上運行。

問題#1:

偶爾(大約每 30 次運行)線程的執行速度會大大減慢。 當 DataReader 獲取存儲過程結果 - SqlCommand.ExecuteReader() 時會發生這種情況。 通常讀取操作在 10 秒內執行。 當它變慢時,它會在 10-20 分鍾內執行。 SQLProfiler 顯示正在查詢數據,但速度很慢。

減速的調用堆棧(請注意,沒有例外):

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()

調用堆棧是使用后台線程中的調試工具獲取的。 不會發生任何異常,無論是減速還是掛斷。

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();
    }
}

我們幾個月來一直試圖調試類似的問題,今天終於找到了它......

我們有一個被隱藏在緩存中的查詢(沒有調用 ToList/ToArray/等)。 該查詢有效地綁定到一個已被清理的連接,我們從ReadSni (包括下面的完整堆棧)獲得了似乎是 100% CPU 阻塞的信息。

我懷疑緩存代碼是在將查詢更改為使用 Linq 之前編寫的(並且用於返回一個List<T> ,但仍然轉換為IEumberable ),因此當有人使數據訪問“惰性”時引入了它。

我無法解釋為什么它只在生產中每隔幾天發生一次; 要么緩存沒有被大量使用,要么連接必須處於某種狀態才能以這種方式失敗。

OS Thread Id: 0x20b8 (27)
Child SP IP       Call Site
16edd0fc 6184267e System.Data.SqlClient.TdsParserStateObject.ReadSni(System.Data.Common.DbAsyncResult, System.Data.SqlClient.TdsParserStateObject)
16edd134 61842624 System.Data.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.ProviderBase.DbConnectionInternal.DeactivateConnection()
16edd1e4 61849405 System.Data.ProviderBase.DbConnectionPool.DeactivateObject(System.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.SqlInternalConnection.CloseConnection(System.Data.Common.DbConnection, System.Data.ProviderBase.DbConnectionFactory)
16edd2c4 618393bf System.Data.SqlClient.SqlConnection.Close()
16edd304 11238f0a NHibernate.Connection.ConnectionProvider.CloseConnection(System.Data.IDbConnection)
16edd340 11238eae NHibernate.Connection.DriverConnectionProvider.CloseConnection(System.Data.IDbConnection)
16edd34c 11aceb42 NHibernate.AdoNet.ConnectionManager.CloseConnection()
16edd358 11aceb02 NHibernate.AdoNet.ConnectionManager.AggressiveRelease()
16edd364 11acf783 NHibernate.AdoNet.ConnectionManager.AfterTransaction()
16edd370 11acf6d1 NHibernate.Impl.SessionImpl.AfterTransactionCompletion(Boolean, NHibernate.ITransaction)
16edd3ec 11acf5de NHibernate.AdoNet.ConnectionManager.AfterNonTransactionalQuery(Boolean)
16edd3fc 11acf539 NHibernate.Impl.AbstractSessionImpl.AfterOperation(Boolean)
16edd474 130311e4 NHibernate.Impl.SessionImpl.List(NHibernate.IQueryExpression, NHibernate.Engine.QueryParameters, System.Collections.IList)
16ede51c 13031071 NHibernate.Impl.AbstractSessionImpl.List(NHibernate.IQueryExpression, NHibernate.Engine.QueryParameters)
16ede538 13030b68 NHibernate.Impl.ExpressionQueryImpl.List()
16ede568 13030a47 NHibernate.Linq.DefaultQueryProvider.ExecuteQuery(NHibernate.Linq.NhLinqExpression, NHibernate.IQuery, NHibernate.Linq.NhLinqExpression)
16ede59c 11d4c163 NHibernate.Linq.DefaultQueryProvider.Execute(System.Linq.Expressions.Expression)
16ede5b0 11d4c108 NHibernate.Linq.DefaultQueryProvider.Execute[[System.__Canon, mscorlib]](System.Linq.Expressions.Expression)
16ede5c4 11d4c0a6 Remotion.Linq.QueryableBase`1[[System.__Canon, mscorlib]].GetEnumerator()
16ede5d4 61022108 System.Linq.Enumerable+WhereEnumerableIterator`1[[System.__Canon, mscorlib]].MoveNext()*** WARNING: Unable to verify checksum for System.Core.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for System.Core.ni.dll

16ede5e4 610166ea System.Linq.Buffer`1[[System.__Canon, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<System.__Canon>)
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<System.__Canon>)*** WARNING: Unable to verify checksum for mscorlib.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for mscorlib.ni.dll

16ede66c 61021acf System.Linq.Enumerable.ToList[[System.__Canon, mscorlib]](System.Collections.Generic.IEnumerable`1<System.__Canon>)

因為代碼可以完美運行一段時間,我們可以將其縮小為:

  • 來自您的進程和其他一些進程的數據庫鎖定/阻塞。
  • 僅從您的進程鎖定/阻止數據庫。
  • 網絡連接。
  • 數據的狀態。
  • 服務器上的磁盤空間或其他一些看似無關的問題。

我會說這很可能是數據庫鎖/塊問題。 但你確實需要確定這一點。 去做這個:

  • 確保有數據庫寫入的磁盤空間 - 包括數據庫日志和任何其他日志記錄。
  • 確保沒有其他進程正在使用該數據庫。
  • 最好也使用本地數據庫來消除網絡問題。
  • 您使用的是 .Net 4 - 所以如果您使用的是任務,那么很容易讓它們與重載同步運行。 這樣做,看看問題是否仍然存在。

執行上述所有操作應該可以消除這些問題 - 您可以從那里開始進一步縮小范圍。

我遇到了同樣的問題,經過幾次測試后我解決了我談論我的具體情況,但我認為它可能有用。

在我的系統中,我在 SQLServer 2008 上有一個復雜的 OLAP 引擎。多年來,操作的數量和數量都在增長,並且隨機出現了帖子中提到的錯誤...引發操作錯誤消失了。 我仔細檢查了所有 OLAP 操作的代碼,所有事務、連接和讀取器對象都得到了完美的處理。

當我在 WHILE 循環中查詢 SQLServer 時,產生錯誤的關鍵點(但並非總是如此),重復的查詢轟炸在 DB 中產生了錯誤

Ho ristrutturato le molte piccole querys in una query piùgrandi(in termini di resultset)ed hooperato delleoperazioni attraverso LINQ, il risultato è stato soprendente。 Perdormance di 10 volte migliorate e 0 errori.

我可以給出的建議是很好地分析代碼的關鍵部分,甚至要非常小心地使用 Connections、DataReader 和 Transactions。

我知道這個問題已經有 8 年歷史了,但也許我的回答會幫助一些將來面臨這個問題的可憐的開發人員:)

就我而言,這是數據庫的死鎖,但不是很明顯。 執行 sp_whoisactive 時,它​​返回許多被單個休眠連接阻塞的行。 最初我認為這可能是由於一些網絡問題,但在使用 Windbg 測試幾個小時並拔掉網線后,我仍然無法重現掛起。

最后,我注意到實際上有 2 個來自阻止用戶的應用程序連接:

  • 掛起的,已經持有一些鎖
  • 另一個被另一個用戶的連接阻止。

結果表明:

  1. 第一個連接被打開,它鎖定了數據庫上的一些對象,但最后它通過 RAISERROR 命令收到一個異常
  2. 同時出現來自其他應用程序的連接,對數據庫進行了一些鎖定,但最終被第一個阻止
  3. 已經建立連接 nr.1 的應用程序使用 RequiresNew 事務選項打開了一個新的,然后被第二個連接阻止

此外,所有 3 個連接都是在 7ms 時間窗口內建立的,這可以解釋為什么幾個月沒有出現這樣的問題。

如果您遇到這個問題,我建議您使用 sp_whoisactive 過程 - http://whoisactive.com您可以使用 @get_locks = 1 參數執行它,它將通過給定的連接返回所有鎖定的對象。 這與調用堆棧相結合應該為您提供足夠的信息來解決此問題。 或者確定是否不是這種情況;)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM