2

我在执行 Sql 时遇到问题,这实际上是对 SqlServer 存储过程的简单调用。

考虑下面的 Sql 存储过程:

    CREATE PROCEDURE InfiniteLoop
    AS
    BEGIN
        DECLARE @ixi NUMERIC(38) = 0
        DECLARE @i NUMERIC(38) = 0
        WHILE 1 = 1
        BEGIN
           SET @i = @i + 1
           SET @ixi = @ixi * @i
           PRINT N'some text'
        END
    END;

现在我以这种方式从 C# 调用此过程:

public void CallProcedure()
{
   SqlCommand cmd = this._connection.CreateCommand();
   cmd.CommandType = CommandType.StoredProcedure;
   command.CommandText = 'InfiniteLoop';

   //Line below throws OutOfMemoryException
   cmd.ExecuteNonQuery();

   cmd.Dispose();
}

内存开始快速增长。几秒钟后抛出异常。通常所有这些代码都使用“try/catch”和“using”部分,但我已经简化了这个代码段以表明问题来自 SqlClient 库而不是直接来自我的代码。

4

1 回答 1

4

经过更多研究,我找到了如何停止 OutOfMemoryException 并采取预期 TimeoutException 的解决方案。

由于存储过程中使用了 PRINT,因此这种情况下的内存正在增长。驱动程序默认从数据库收集输出。因此,如果用户没有阅读此内容,则可能会发生 OutOfMemoryException。

根据您想要的结果,可以使用两种解决方案。

当数据库输出对您不重要并且您期望在执行需要很长时间时出现超时时,第一个很好。下面的片段以这种方式解决了问题:

public void CallProcedure()
{
   // Line below release all Errors/PRINT output from command. Command is now
   // not collecting them, so memory is not growing. 
   // CommandTimeout will be thrown after preset value on command object.
   this._connection.FireInfoMessageEventOnUserErrors = true;

   SqlCommand cmd = this._connection.CreateCommand();
   cmd.CommandTimeout = 15;
   cmd.CommandType = CommandType.StoredProcedure;
   command.CommandText = 'InfiniteLoop';

   //Line below throws OutOfMemoryException
   cmd.ExecuteNonQuery();

   cmd.Dispose();
}

第二个是goog,当您想要执行可能需要大量时间的真正耗时的程序时。永远不会发生超时异常。要启用此行为,您需要在 SqlConnection 中的 InfoMessage 上附加 SqlInfoMessageEventHandler。请参见下面的片段:

public void CallProcedure()
{
   // By attaching this event no Timeout exception on ExecuteNonQuery occur
   // Your ExecuteNonQuery can hang infinitly!
   // If you need Timeout then you need to write you own handler from different thread
   this._connection.InfoMessage += new SqlInfoMessageEventHandler(OnInfoMessage);

   // Line below release all Errors/PRINT output from command. Command is now
   // not collecting them so memory is not growing.
   this._connection.FireInfoMessageEventOnUserErrors = true;

   SqlCommand cmd = this._connection.CreateCommand();

   // In this case Timeout will never occur
   cmd.CommandTimeout = 15;
   cmd.CommandType = CommandType.StoredProcedure;
   command.CommandText = 'InfiniteLoop';

   //Line below throws OutOfMemoryException
   cmd.ExecuteNonQuery();

   cmd.Dispose();

   this._connection.InfoMessage -= new SqlInfoMessageEventHandler(OnInfoMessage);
}

void OnInfoMessage(object sender, SqlInfoMessageEventArgs e)
{
   System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString()+": "+ e.Message);
}
于 2013-03-20T07:14:20.090 回答