【问题标题】:ADO.Net SQLCommand.ExecuteReader() slows down or hangsADO.Net SQLCommand.ExecuteReader() 变慢或挂起
【发布时间】:2012-08-20 01:05:40
【问题描述】:

环境:

应用程序(用 C# 为 .Net 4 编写)最多有 10 个线程,每个线程在其自己的 AppDomain 中运行。每个线程都使用 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()

调用堆栈是在后台线程中使用调试工具获取的。不会发生任何异常,无论是减速还是挂断。

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

【问题讨论】:

  • 你能显示代码吗?尤其是在您创建、打开、关闭连接和执行阅读器的地方。
  • 这听起来更像是数据库方面的问题。 sql上是否有一些定期运行的工作进程?导致行/表锁定的同步或某些操作?涉及的表有多大?什么样的索引/约束等。
  • @TimSchmelter - 我附加了挂起的代码。
  • 您估计通常会传输多少行?
  • 什么是 SqlCommand,因为 ADO.NET SqlCommand 没有 Connector(但 Connection)属性?为什么要使用SqlConnection "_сonnectionBuilk = ... ; // 单例连接"? ConnectionPool 是否通过ConnectionString 启用?我假设你的单例连接是静态的,因此池不能为多个线程重用一个连接,并且总是需要创建一个新的物理连接。

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


【解决方案1】:

几个月来我们一直在尝试调试一个类似的问题,今天终于找到了它......

我们有一个查询被隐藏到缓存中(没有调用 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>)

【讨论】:

  • 你能解决这个问题吗?如果是这样,是 SQL 服务器端、ADO.NET 端的配置问题还是代码更改?
  • 代码更改。我们在将项目放入缓存之前调用了 ToList(),因此实际项目被缓存而不是尚未评估的枚举。不要认为我们从那以后就没有见过它:-)
【解决方案2】:

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

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

我会说这可能是数据库锁定/阻塞问题。但是您确实需要确定这一点。为此:

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

执行上述所有操作应该可以消除这些问题 - 您可以从那里开始进一步缩小范围。

【讨论】:

    【解决方案3】:

    我遇到了同样的问题,经过几次测试后我解决了我的具体情况,但我认为它可能有用。

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

    当我在 WHILE 循环中查询 SQLServer 时产生错误的关键点(但并非总是如此),查询的重复轰炸在 DB 中产生错误

    Ho ristrutturato le molte piccole 在 una query più grandi(in termini di resultset) ed ho operato delleoperazioni attraverso LINQ, il risultato è stato soprendente 中查询。性能 di 10 volte migliorate e 0 errori.

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

    【讨论】:

      【解决方案4】:

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

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

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

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

      原来是这样的:

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

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

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

      【讨论】:

        猜你喜欢
        • 2017-07-03
        • 2019-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-15
        相关资源
        最近更新 更多