ADO.Net SQLCommand.ExecuteReader()减慢或挂起

时间:2012-08-24 12:58:56

标签: c# .net sql sql-server ado.net

环境:

应用程序(用C#编写的.Net 4)最多有10个线程,每个线程都在自己的AppDomain中运行。每个线程使用一个ADO.Net DataReader,它从SQL-Server 2008上的存储过程中获取结果。另外一个线程可以使用ADO.Net来执行写操作(Bulk Insert)。一切都在本地机器上运行。

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

4 个答案:

答案 0 :(得分:3)

我们几个月来一直试图调试类似的问题,并最终在今天跟踪它......

我们有一个被隐藏在缓存中的查询(没有calliong ToList / ToArray / etc。)。该查询实际上与已经清理过的连接有关,我们从[code] ReadSni [/ code](下面的完整堆栈)中得到了100%CPU阻塞的内容。

我怀疑缓存代码是在查询被更改为使用Linq之前编写的(并且过去常常返回一个List,但仍然被转换为IEumberable)所以当有人使数据访问“懒惰”时引入了缓存代码。

我无法解释为什么它只在生产中每隔几天发生一次;要么缓存没有被大量使用,要么连接必须处于某种状态才能以这种方式失败。

[代码] 操作系统线程ID:0x20b8(27) 儿童SP IP呼叫站点 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] 16ede5c4 11d4c0a6 Remotion.Linq.QueryableBase 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“&gt; [System .__ Canon,mscorlib] [/代码]

答案 1 :(得分:1)

因为代码在一段时间内完美运行,我们可以将其缩小到:

  • 数据库锁定/阻止您的进程和其他一些进程。
  • 仅限您的流程中的数据库锁定/阻止。
  • 网络连接。
  • 数据状态。
  • 服务器上的磁盘空间或其他一些看似无关的问题。

我认为这可能是数据库锁定/阻止问题。但你确实需要确定这一点。要做到这一点:

  • 确保数据库正在写入磁盘空间 - 包括数据库日志和任何其他日志记录。
  • 确保没有其他进程正在使用该数据库。
  • 最好使用本地数据库来消除网络问题。
  • 您正在使用.Net 4 - 因此,如果您正在使用任务,那么很容易使它们与重载同步运行。这样做,看看问题是否仍然存在。

完成上述所有操作应该可以解决问题 - 您可以从那里开始进一步缩小范围。

答案 2 :(得分:0)

我遇到了同样的问题,经过多次测试后我解决了我的具体案例,但我认为这可能很有用。

在我的系统中,我在SQLServer 2008上有一个复杂的OLAP引擎。 多年来,操作量和数量都在增加,我随机得到了帖子中提到的错误...提高操作错误就消失了。我仔细检查了所有操作OLAP的代码,并完美地处理了所有事务,连接和读取器对象。

当我是WHILE循环的部分来查询SQLServer时,产生错误(但并不总是)的关键点,重复轰炸查询在DB中生成错误

Ho ristrutturato le molte piccole querys in unaquerypiùgrampi(in termini di resultset)ed ho operato delle operazioni attraverso LINQ,ilrisultatoèstodesoprendente。 Perdormance di 10 volte migliorate e 0 errori。

我可以给出的建议是很好地分析代码的关键部分,甚至非常小心你如何使用Connections,DataReader和Transactions。

答案 3 :(得分:0)

我知道这个问题已经有 8 年历史了,但也许我的回答会帮助一些未来面临这个问题的可怜的开发者:)

就我而言,这是数据库死锁,但不是很明显。 执行 sp_whoisactive 时,它​​返回许多被单个休眠连接阻塞的行。一开始我认为可能是一些网络问题,但在用windbg测试几个小时并拔掉网线后,我仍然无法重现挂起。

最后我注意到实际上有 2 个来自阻止用户的应用连接:

  • 暂停的,已经持有一些锁
  • 另一个被其他用户的连接阻止的。

事实证明:

  1. 第一个连接被打开,它锁定了数据库上的一些对象,但最后它通过 RAISERROR 命令收到一个异常
  2. 同时出现来自其他应用的连接,对数据库进行了一些锁定,但最终被第一个阻止
  3. 已经建立连接 nr.1 的应用程序使用 RequiresNew 事务选项打开了一个新的,然后被第二个连接阻止

此外,所有 3 个连接都是在 7 毫秒的时间窗口内建立的,这可以解释为什么几个月没有出现此类问题。

如果您遇到这个问题,我建议您使用 sp_whoisactive 程序 - http://whoisactive.com 您可以使用@get_locks = 1 参数执行它,它将通过给定的连接返回所有锁定的对象。这与调用堆栈相结合应该为您提供足够的信息来解决此问题。或者确定是否不是这种情况;)