10

环境:

应用程序(用 C# 为 .Net 4 编写)最多有 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();
    }
}
4

4 回答 4

4

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

我们有一个查询被隐藏到缓存中(没有调用 ToList/ToArray/等)。该查询有效地绑定到一个已被清理的连接,我们得到了似乎是 100% CPU 阻塞的内容ReadSni(完整堆栈包括在下面)。

我怀疑缓存代码是在查询更改为使用 Linq 之前编写的(并且过去常常返回 a 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>)
于 2012-09-17T13:08:11.237 回答
1

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

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

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

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

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

于 2012-08-26T22:17:03.943 回答
0

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

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

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

Ho ristrutturato le molte piccole querys in 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。

于 2014-08-28T07:43:02.903 回答
0

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

就我而言,这是数据库上的死锁,但不是很明显。当 sp_whoisactive 被执行时,它返回了许多被单个睡眠连接阻塞的行。最初我认为这可能是由于某些网络问题,但是在使用 windbg 进行了几个小时的测试并拔下网络电缆后,我仍然无法重现挂起。

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

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

事实证明:

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

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

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

于 2021-02-16T20:13:59.810 回答