Oracle не удаляет курсоры после закрытия набора результатов
Примечание: мы используем одно соединение.
************************************************
public Connection connection() {
try {
if ((connection == null) || (connection.isClosed()))
{
if (connection!=null)
log.severe("Connection was closed !");
connection = DriverManager.getConnection(jdbcURL, username, password);
}
} catch (SQLException e) {
log.severe("can't connect: " + e.getMessage());
}
return connection;
}
**************************************************
public IngisObject[] select(String query, String idColumnName, String[] columns) {
Connection con = connection();
Vector<IngisObject> objects = new Vector<IngisObject>();
try {
Statement stmt = con.createStatement();
String sql = query;
ResultSet rs =stmt.executeQuery(sql);//oracle increases cursors count here
while(rs.next()) {
IngisObject o = new IngisObject("New Result");
o.setIdColumnName(idColumnName);
o.setDatabase(this);
for(String column: columns)
o.attrs().put(column, rs.getObject(column));
objects.add(o);
}
rs.close();// oracle don't decrease cursor count here, while it expected
stmt.close();
}
catch (SQLException ex) {
System.out.println(query);
ex.printStackTrace();
}
Ответы
Ответ 1
Параметр init.ora open_cursors
определяет максимум открытых курсоров, которые могут иметь сеансы одновременно. Он имеет значение по умолчанию 50. Если приложение превышает этот номер, повышается ошибка "ORA-01000: максимальные открытые курсоры".
Поэтому обязательно закрыть ресурсы JDBC, когда они больше не нужны, в частности java.sql.ResultSet и java.sql.Statement. Если они не закрыты, приложение имеет утечку ресурсов.
В случае повторного использования объекта Connection вы должны быть осведомлены о том, что открытые курсоры оракула остаются открытыми и используются до тех пор, пока соединение существует и транзакция еще не закончилась. Когда приложение завершается, открытые курсоры освобождаются.
Поэтому в качестве разработчика приложений вам нужно знать приблизительную оценку необходимых открытых курсоров для наиболее сложной транзакции.
Трудность заключается в невозможности просмотра внутренних параметров оракула (v $open_cursor, v $sesstat и др.), чтобы показать разницу между открытыми курсорами, которые являются многоразовыми и открытыми курсорами, которые все еще блокированы (не могут использоваться повторно!) незакрытым ResulSet или Statement. Если вы закроете все объекты Statement и ResultSet в своем блоке finally, ваше приложение будет прекрасно.
Настройка параметра init.ora работает так (наше приложение требует максимум 800 курсоров)
ALTER SYSTEM SET open_cursors = 800 SCOPE=BOTH;
Ответ 2
Обычно вы добавляете операторы закрытия для своего ResultSet и Statement в блок finally
, чтобы гарантировать, что они вызываются, даже если возникает исключение (может быть, проблема, которую вы здесь имеете). В вашем текущем коде, если происходит SQLException, вызовы метода close() никогда не будут выполняться, и курсоры будут оставлены открытыми.
И какой запрос вы используете в Oracle, чтобы увидеть количество открытых курсоров?
Edit:
Этот код должен закрывать курсор. Если это не так, вы должны иметь возможность видеть соотношение 1:1, вызывающее ваш метод, и количество курсоров, увеличивающееся на 1. Убедитесь, что нет какого-либо неожиданного процесса, который вызывает увеличение числа курсоров.
Если у вас есть привилегии, вы можете запустить этот запрос с базой данных, чтобы просмотреть счетчик открытых курсоров по sid, чтобы узнать, может быть, это какой-то другой процесс, который увеличивает курсоры, а не ваши. Он отталкивает все, если открыто более 10 курсоров, вы можете поднять это, чтобы отфильтровать шум или сузить его по имени пользователя или osuser:
select oc.sid,
count(*) numCur,
s.username username,
s.osuser osuser,
oc.sql_text,
s.program
from v$open_cursor oc,
v$session s
where s.sid = oc.sid
group by oc.sid,
oc.sql_text,
s.username,
s.osuser,
s.program
having count(*) > 10
order by oc.sid;
Другой запрос, который может быть полезен, в случае, если несколько sid используют одну и ту же строку запроса, поэтому вышеописанное не выявляет хорошо обитателя:
select oc.sql_text, count(*)
from v$open_cursor oc
group by oc.sql_text
having count(*) > 10
order by count(*) desc;
Ответ 3
Правильный способ сделать это - закрыть каждый ресурс в блоке finally в своем собственном блоке try/catch. Обычно я использую статический класс утилиты:
public class DatabaseUtils
{
public static void close(Connection connection)
{
try
{
if (connection != null)
{
connection.close();
}
}
catch (SQLException e)
{
// log exception here.
}
}
// similar methods for ResultSet and Statement
}
Итак, я бы написал ваш код следующим образом:
public IngisObject[] select(String query, String idColumnName, String[] columns) {
Vector<IngisObject> objects = new Vector<IngisObject>();
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try
{
connection = connection();
stmt = con.createStatement();
// This is a SQL injection attack waiting to happen; I'd recommend PreparedStatemen
String sql = query;
rs =stmt.executeQuery(sql);//oracle increases cursors count here
while(rs.next())
{
IngisObject o = new IngisObject("New Result");
o.setIdColumnName(idColumnName);
o.setDatabase(this);
for(String column: columns) o.attrs().put(column, rs.getObject(column));
objects.add(o);
}
}
catch (SQLException ex)
{
System.out.println(query);
ex.printStackTrace();
}
finally
{
DatabaseUtils.close(rs);
DatabaseUtils.close(stmt);
DatabaseUtils.close(con);
}
Ответ 4
У меня была одна и та же проблема, и я обнаружил, что если вы не закрываете соединение (потому что позже вы можете его повторно использовать), вам, по крайней мере, нужно сделать connection.rollback() или connection.commit(), чтобы освободить открытые курсоры togehther с закрытием ResultSet и Statement.