开发者

Oracle ODP.NET Cursor Leak?

I'm running into an open cursor limit issue with using the following code. The open cursor limit on the oracle db is set to around 1000. The following code seems to hold onto the cursors even though I've got everything in a using statement (I think) that requires it. (Note, I don't need to read anything from outRefCursor2)

Am I missing a using or some other clean up with ODP.net?

The exception occurs consistently at iteration 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 item开发者_运维知识库s) {
        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;
}

I'm using this snippet found online to monitor DB cursors. I can watch the cursors add up while stepping through the code. And they just keep adding at the cmd.ExecuteNonQuery() line. I never see a drop after any using statement closes.

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;


Even though you're not using outRefCursor2 you still need to extract it and close it if it returns a valid cursor. ODP.net doesn't dispose of resources as nicely as the .Net version did so you need to make sure you dispose everything that is returned by ODP.net commands. As an extra step, it may not hurt to go and call .Close() explicitly on the cursors either to ensure that you're actually closing them (though the dispose should take care of that).


You need to dispose of the parameters:

  • yes I confirmed this before showing native resource leaks (Virtual working set) when we didn't
  • I prefer to dispose of the params within the life-time of the connection so as to prevent any issues when the ref cursors need/want to use the connection on dispose (superstition, probably)

    static List GetDetailsForItems(List items) { 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;
    

    }


I wouldn't go for GC.collect()... It is an overkill...http://blogs.msdn.com/b/scottholden/archive/2004/12/28/339733.aspx

But making sure disposing the command object worked for me. Easy is to use the "Using"

Something like this:

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


None of the suggestions had worked thus far. So in desperation, I ended up force GC collection every 200 iterations. With the following code.

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

What's strange is that when calling this method from a unit test, the manual GC.Collect() does not release any cursors. But when calling the method from the business layer, it actually does work and I can see the open cursors get released by monitoring the oracle DB.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜