Как принудительно закрыть соединение из пула соединений, если закрытие занимает слишком много времени?

Бывают случаи, когда закрытие соединений занимает много времени, например, от 10 минут до 1 часа или еще хуже, даже на неопределенное время, в зависимости от того, насколько тяжелым или медленным был запрос.

В ситуации, когда клиент отменяет запрос, потому что он занимает слишком много времени, я хотел бы как можно скорее освободить используемое базовое соединение.

Я попытался отменить PreparedStatement, закрыть его, затем закрыть набор результатов и, наконец, закрыть соединение. Отмена прошла почти мгновенно. Закрытие PreparedStatement и ResultSet заняло слишком много времени, поэтому мне пришлось обернуть его в Callable с тайм-аутом, чтобы вовремя пропустить этот процесс и продолжить закрытие самого соединения. Мне не повезло, что еще можно попробовать.

Как мне справиться с этим? Я не могу просто оставить соединения незакрытыми и не могу позволить пользователям ждать 10 минут, прежде чем они смогут сделать еще один аналогичный запрос.

Кроме того, что приводит к тому, что закрытие соединения занимает слишком много времени? Есть ли что-нибудь еще, что я мог бы сделать? Как вы думаете, помогут ли подсказки запросов Oracle?

Кстати, я использую Oracle JDBC через драйвер тонкого типа.

ОБНОВИТЬ:

По-видимому, можно принудительно закрыть соединение, настроив свойство TimeToLive в connectionCacheProperties, которое закрывает соединение на определенное время. Тем не менее, то, что мне нужно, по мере необходимости. Это стоит упомянуть, потому что это доказывает, что его можно принудительно закрыть, как это только что сделал пул соединений. На самом деле, я даже получил следующее сообщение в своих журналах.

ORA-01013: user requested cancel..

person supertonsky    schedule 06.06.2017    source источник
comment
Я никогда не слышал о таком сценарии, когда для закрытия соединения требуется время.   -  person OldProgrammer    schedule 06.06.2017
comment
@OldProgrammer, попробуйте запросить что-то достаточно сложное, что даже получение первых нескольких записей из набора результатов занимает слишком много времени. Давайте не будем вдаваться в аспект производительности этой проблемы. Я заметил, что как только запрос получает несколько строк, он быстро закрывается. Я предполагаю, что запрос перешел в фазу, когда он не может быть закрыт (но на самом деле это не так, достижение TimeToLive может закрыть его), прежде чем перейти к другой фазе, когда он начинает отправлять записи, которые можно закрыть.   -  person supertonsky    schedule 06.06.2017
comment
Вы можете попытаться прервать его из другого Oracle SPID (давайте назовем его подключением администратора вашего приложения), используя оператор kill.   -  person access_granted    schedule 07.06.2017
comment
@access_granted, не могли бы вы перевести это как ответ и использовать программное решение Java с использованием JDBC? Я приму это, если это сработает.   -  person supertonsky    schedule 07.06.2017


Ответы (2)


Основная функция:

  String g_sid = "";

Тема 1:

  String sql = ...;
  Connection conn = ...your connection func...;

  Statement stmt = conn.createStatement();
  ResultSet rset = stmt.executeQuery( "SELECT sid from v$mystat");
  if (rset.next()) g_sid = rset.getString("sid");
  rset.close();
  // now to the actual long-running SQL
  ResultSet rset = stmt.executeQuery( sql );
  // 
  stmt.close();

Тема 2:

  String serialN = "";
  Connection conn = ...your admin connection func...

  Statement stmt = conn.createStatement();
  ResultSet rset = stmt.executeQuery( "SELECT serial# serialN from v$session where sid=" + g_sid );
  if (rset.next()) {
    serialN = rset.getString("serialN"); 
    stmt.execute("alter system kill session '" + g_sid + "," + serialN + "'");
  }
  stmt.close();
  // probably keep the admin connection open for further maintenance
  //
person access_granted    schedule 07.06.2017
comment
Здесь нет JSP, это чистая Java. - person user207421; 07.06.2017
comment
так что просто поделитесь переменной SID между потоками в таком случае - person access_granted; 07.06.2017
comment
Теперь похоже, что для этого потребуется доступ с более высокими привилегиями. Я попробую. Я действительно предпочитаю непривилегированный способ сделать это. - person supertonsky; 07.06.2017
comment
ну, ваш администратор базы данных либо должен разрешить вам изменять систему, либо должен будет скомпилировать небольшую процедуру SYS, выполняющую это неявно. Как только Oracle вылупится из запущенного процесса, на самом деле либо изменение системы, либо уничтожение spid Unix (тем не менее принадлежащего oracle uid) выпустит его (и я только что отредактировал stmt с кавычками) - person access_granted; 07.06.2017
comment
@access_granted, на самом деле оба числа должны быть заключены в одну пару кавычек. Я знал, что получу ошибку SQL: ORA-01031: недостаточно прав. Думаю, мне нужно изменить свой вопрос. - person supertonsky; 07.06.2017
comment
@access_granted, если когда-нибудь администратор базы данных действительно создаст это, я думаю, как бы он создал некоторые проверки, которые не будут вызываться для сеансов, которыми я не владею? Это может быть сложно реализовать. - person supertonsky; 07.06.2017
comment
Я только что выяснил, что на самом деле у нас есть политика всей фирмы, чтобы не создавать такие хранимые процедуры и команды, которые убивают сеансы, должны пройти специальный процесс. Тем не менее +1 за предоставление альтернативного решения, хотя и не идеального. - person supertonsky; 07.06.2017

Это то, что мы используем в моем POW вместо «предоставить изменение системы для $UID» в качестве процедуры, принадлежащей SYS (упрощенная рабочая версия):

CREATE OR REPLACE procedure SYS.kill_session(in_sid varchar2)
as
  l_serial number;
  l_runsql varchar2(1000) := 'alter system kill session ''$1,$2'' immediate';
begin
  begin
  select serial# into l_serial from v$session where username =
  (
    SELECT USER FROM DUAL
  ) and sid=in_sid and rownum<=1;
  exception when no_data_found then
    raise_application_error( -20001, 'Kill candidate not found');
  end;
  l_runsql := replace( l_runsql, '$1', in_sid);
  l_runsql := replace( l_runsql, '$2', l_serial);
  execute immediate l_runsql;
end;
/

Таким образом, вы можете убить только свои собственные сеансы.

person access_granted    schedule 07.06.2017
comment
Я только что выяснил, что на самом деле у нас есть политика всей фирмы, чтобы не создавать такие хранимые процедуры и команды, которые убивают сеансы, должны пройти специальный процесс. Тем не менее +1 за предоставление альтернативного решения, хотя и не идеального и работоспособного решения в моей текущей ситуации. - person supertonsky; 07.06.2017