Oracle ODP.. NET游标泄漏

本文关键字:泄漏 游标 NET ODP Oracle | 更新日期: 2023-09-27 18:10:10

我在使用以下代码时遇到了打开游标限制问题。oracle数据库上的打开游标限制设置为1000左右。下面的代码似乎抓住了游标,即使我已经在一个using语句(我认为),需要它的一切。(注意,我不需要从outRefCursor2中读取任何内容)

我错过了using或其他一些清理与ODP.net?
异常在第596次迭代时始终发生。

static List<Thing> GetDetailsForItems(List<string> items) {
  DateTime start = DateTime.UtcNow;
  var things = new List<Thing>();
  var spname = "SP_GET_THING_DETAILS";
  var outRefCursorName1 = "p_ref_cursor1";
  var outRefCursorName2 = "p_ref_cursor2";
  // Create params
  var pInput1 = new OracleParameter("p_input1",
                  OracleDbType.Varchar2, ParameterDirection.Input);
  pInput1.Value = "";
  // Input 2 can be blank
  var pInput2 = new OracleParameter("p_input2",
                  OracleDbType.Varchar2, ParameterDirection.Input);
  pInput2.Value = "";
  var outRefCursor1 = new OracleParameter(outRefCursorName1,
                  OracleDbType.RefCursor, ParameterDirection.Output);
  var outRefCursor2 = new OracleParameter(outRefCursorName2,
                  OracleDbType.RefCursor, ParameterDirection.Output);
  int count = 0;
  using (var conn = new OracleConnection(CONN_STR)) {
    conn.Open();
    using (var cmd = conn.CreateCommand()) {
      cmd.Parameters.Add(pInput1);
      cmd.Parameters.Add(pInput2);
      cmd.Parameters.Add(outRefCursor1);
      cmd.Parameters.Add(outRefCursor2);
      cmd.CommandText = spname;
      cmd.CommandType = CommandType.StoredProcedure;
      foreach (string value in items) {
        count++; 
        cmd.Parameters[pInput1.ParameterName].Value = value;
        var execVal = cmd.ExecuteNonQuery();
        using (var refCursor = (Types.OracleRefCursor)
                                cmd.Parameters[outRefCursorName1].Value) {
          using (var reader = refCursor.GetDataReader()) {
            while (reader.Read()) {
              // read columns
              things.Add(reader["COLUMN_A"].ToString());
            }
          } // close reader
        } // close cursor
      } // end foreach
    } // close command
  } // close connection           
  int seconds = (DateTime.UtcNow - start).Seconds;
  Console.WriteLine("Finished in {0} seconds", seconds);
  return things;
}

我使用在线找到的这个代码片段来监视DB游标。在执行代码时,我可以看到游标加起来。它们一直在cmd.ExecuteNonQuery()线上相加。我从来没有在using语句结束后看到drop。

select sum(a.value) total_cur, avg(a.value) avg_cur, max(a.value) max_cur, 
s.username, s.machine
from v$sesstat a, v$statname b, v$session s 
where a.statistic# = b.statistic#  and s.sid=a.sid
and b.name = 'opened cursors current'
and machine='MY COMPUTER' 
group by s.username, s.machine
order by 1 desc;

Oracle ODP.. NET游标泄漏

即使您不使用outRefCursor2,您仍然需要提取它并关闭它,如果它返回一个有效的游标。ODP.net不像。net版本那样很好地处理资源,因此您需要确保处理由ODP.net命令返回的所有。作为额外的步骤,在游标上显式地调用.Close()以确保您实际上关闭了它们(尽管dispose应该负责这一点)。

您需要处理参数:

  • 是的,当我们没有
  • 时,我在显示本机资源泄漏(虚拟工作集)之前确认了这一点
  • 我倾向于在连接的生命周期内处理参数,以防止当引用需要/想要在处理时使用连接时出现任何问题(可能是迷信)

    static List GetDetailsForItems(列表项){DateTime start = DateTime. utcnow;var things = new List();var spname = "SP_GET_THING_DETAILS";var outRefCursorName1 = "p_ref_cursor1";var outRefCursorName2 = "p_ref_cursor2";

    try
    {
        int count = 0;
        using (var conn = new OracleConnection(CONN_STR))
        try
        {
            conn.Open();
            // Create params
            var pInput1 = new OracleParameter("p_input1", OracleDbType.Varchar2, ParameterDirection.Input);
            pInput1.Value = "";
            // Input 2 can be blank
            var pInput2 = new OracleParameter("p_input2", OracleDbType.Varchar2, ParameterDirection.Input);
            pInput2.Value = "";
            var outRefCursor1 = new OracleParameter(outRefCursorName1, OracleDbType.RefCursor, ParameterDirection.Output);
            var outRefCursor2 = new OracleParameter(outRefCursorName2, OracleDbType.RefCursor, ParameterDirection.Output);
            using (var cmd = conn.CreateCommand())
            {
                cmd.Parameters.Add(pInput1);
                cmd.Parameters.Add(pInput2);
                cmd.Parameters.Add(outRefCursor1);
                cmd.Parameters.Add(outRefCursor2);
                cmd.CommandText = spname;
                cmd.CommandType = CommandType.StoredProcedure;
                foreach (string value in items)
                {
                    count++;
                    cmd.Parameters[pInput1.ParameterName].Value = value;
                    var execVal = cmd.ExecuteNonQuery();
                    using (var refCursor = (Types.OracleRefCursor)
                                           cmd.Parameters[outRefCursorName1].Value)
                    {
                        using (var reader = refCursor.GetDataReader())
                        {
                            while (reader.Read())
                            {
                                // read columns
                                things.Add(reader["COLUMN_A"].ToString());
                            }
                        } // close reader
                    } // close cursor
                } // end foreach
            } // close command
        } // close connection
        finally
        {
            pInput1.Dispose();
            pInput2.Dispose();
            outRefCursorName1.Dispose();
            outRefCursorName2.Dispose();
        }
    }
    int seconds = (DateTime.UtcNow - start).Seconds;
    Console.WriteLine("Finished in {0} seconds", seconds);
    return things;
    

    }

我不会选择GC.collect()…这太过分了…http://blogs.msdn.com/b/scottholden/archive/2004/12/28/339733.aspx

但是要确保处理命令对象对我有效。简单的是使用"Using"

像这样:

using(DbCommand command = dbConn1.CreateCommand())
{
    command.CommandText = sql;
    using (var dataReader = command.ExecuteReader())
    {
        dbRows = ToList(dataReader);
    }
    mvarLastSQLError = 0;
}

到目前为止,这些建议都没有奏效。所以在绝望中,我最后每200次迭代强制GC收集一次。使用以下代码:

if (count % 200 == 0) {
    GC.Collect();
}

奇怪的是,当从单元测试中调用该方法时,手动GC.Collect()不释放任何游标。但是当从业务层调用该方法时,它实际上确实工作,并且我可以看到通过监视oracle DB释放打开的游标。