Ответ 1
ORA-01000, ошибка с максимальным открытием-курсором, является чрезвычайно распространенной ошибкой в разработке базы данных Oracle. В контексте Java это происходит, когда приложение пытается открыть больше ResultSets, чем настроены курсоры в экземпляре базы данных.
Общие причины:
-
Ошибка конфигурации
- У вас больше потоков в приложении, запрашивающих базу данных, чем курсоры в БД. В одном случае у вас есть соединение и пул потоков больше, чем число курсоров в базе данных.
- У вас много разработчиков или приложений, подключенных к одному экземпляру DB (который, вероятно, будет включать в себя множество схем), и вместе вы используете слишком много соединений.
-
Решение:
- Увеличение количества курсоров в базе данных (если позволяют ресурсы) или
- Уменьшение количества потоков в приложении.
-
Утечка курсора
- Приложения не закрывают ResultSets (в JDBC) или курсоры (в хранимых процедурах в базе данных)
- Решение: утечки курсора - это ошибки; увеличение числа курсоров на БД просто задерживает неизбежный сбой. Утечки можно найти, используя статический анализ кода, JDBC или регистрации на уровне приложений, и мониторинг базы данных.
Фон
В этом разделе описывается некоторая теория курсоров и использование JDBC. Если вам не нужно знать фон, вы можете пропустить это и перейти прямо к "Устранение утечек".
Что такое курсор?
Курсор - это ресурс в базе данных, который содержит состояние запроса, а именно позицию, в которой читатель находится в ResultSet. Каждый оператор SELECT имеет курсор, и хранимые процедуры PL/SQL могут открывать и использовать столько курсоров, сколько им требуется. Вы можете узнать больше о курсорах на Orafaq.
Экземпляр базы данных, как правило, обслуживает несколько разных схем, много разных пользователей, каждый из которых имеет несколько сеансов. Для этого у него есть фиксированное количество курсоров, доступных для всех схем, пользователей и сеансов. Когда все курсоры открыты (используются), и запрос приходит, в котором требуется новый курсор, запрос выходит из строя с ошибкой ORA-010000.
Поиск и установка числа курсоров
Номер обычно настраивается администратором базы данных при установке. Количество используемых курсоров, максимальное количество и конфигурация могут быть доступны в функциях администратора в Oracle SQL Developer. Из SQL он может быть установлен с помощью:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Связывание JDBC в JVM с курсорами на DB
Объекты JDBC ниже тесно связаны со следующими концепциями баз данных:
- JDBC Connection - это клиентское представление сеанса базы данных и обеспечивает транзакции базы данных. Соединение может иметь только одну транзакцию, открытую в любой момент времени (но транзакции могут быть вложенными).
- Функция JDBC ResultSet поддерживается одним курсором в базе данных. Когда в ResultSet вызывается функция close(), курсор отпускается.
- JDBC CallableStatement вызывает хранимую процедуру в базе данных, часто написанную в PL/SQL. Хранимая процедура может создавать ноль или более курсоров и может возвращать курсор в качестве набора результатов JDBC.
JDBC является потокобезопасным: вполне нормально передавать различные объекты JDBC между потоками.
Например, вы можете создать соединение в одном потоке; другой поток может использовать это соединение для создания PreparedStatement, а третий поток может обрабатывать набор результатов. Единственное серьезное ограничение состоит в том, что вы не можете открыть более одного ResultSet на одном PreparedStatement в любое время. См. Поддерживает ли Oracle DB несколько (параллельных) операций для каждого соединения?
Обратите внимание, что в соединении происходит фиксация базы данных, и поэтому все DML (INSERT, UPDATE и DELETE) в этом соединении свяжутся вместе. Поэтому, если вы хотите поддерживать несколько транзакций одновременно, у вас должно быть хотя бы одно соединение для каждой параллельной транзакции.
Закрытие объектов JDBC
Типичным примером выполнения ResultSet является:
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery( "SELECT FULL_NAME FROM EMP" );
try {
while ( rs.next() ) {
System.out.println( "Name: " + rs.getString("FULL_NAME") );
}
} finally {
try { rs.close(); } catch (Exception ignore) { }
}
} finally {
try { stmt.close(); } catch (Exception ignore) { }
}
Обратите внимание, что в предложении finally игнорируется какое-либо исключение, вызванное функцией close():
- Если вы просто закроете ResultSet без try {} catch {}, он может выйти из строя и запретить закрытие Заявления
- Мы хотим разрешить любое исключение, поднятое в теле try, распространяться на вызывающего. Если у вас есть цикл, например, создание и выполнение выражений, не забудьте закрыть каждое выражение в цикле.
В Java 7 Oracle внедрил AutoCloseable interface, который заменяет большую часть шаблона Java 6 приятным синтаксическим сахаром.
Удержание объектов JDBC
Объекты JDBC можно безопасно удерживать в локальных переменных, экземпляре объекта и членах класса. Как правило, лучше:
- Использовать экземпляр объекта или членов класса для хранения объектов JDBC, которые многократно используются многократно в течение более длительного периода, таких как Connections и PreparedStatements
- Используйте локальные переменные для ResultSets, поскольку они получены, зацикливаются и затем закрываются, как правило, в пределах одной функции.
Однако есть одно исключение: если вы используете EJB или контейнер Servlet/JSP, вы должны следовать строгой модели потоков:
- Только сервер приложений создает потоки (с которыми он обрабатывает входящие запросы)
- Только сервер приложений создает подключения (которые вы получаете из пула соединений)
- При сохранении значений (состояния) между вызовами вы должны быть очень осторожны. Никогда не храните значения в своих собственных кешах или статических членах - это небезопасно для кластеров и других странных условий, а сервер приложений может создавать ужасные вещи для ваших данных. Вместо этого используйте stateful beans или базу данных.
- В частности, никогда не держите объекты JDBC (Connections, ResultSets, PreparedStatements и т.д.) по разным удаленным вызовам - пусть Application Server управляет этим. Сервер приложений не только предоставляет пул соединений, но также кэширует ваши PreparedStatements.
Устранение утечек
Существует ряд процессов и инструментов для обнаружения и устранения утечек JDBC:
-
Во время разработки - ловушка ошибок на ранней стадии является наилучшим подходом:
-
Методы разработки: Хорошие методы разработки должны уменьшить количество ошибок в вашем программном обеспечении до того, как он покинет рабочий стол разработчика. Конкретная практика включает в себя:
- Паровое программирование, чтобы обучить тех, у кого нет достаточного опыта.
- Обзор кода, потому что многие глаза лучше, чем один
- Тестирование устройств, что означает, что вы можете использовать любую и всю вашу базу кода из тестового инструмента, который делает воспроизведение утечек тривиальным.
- Используйте существующие библиотеки для пула соединений, а не для создания собственных
-
Анализ статического кода: используйте инструмент, отличный от Findbugs, чтобы выполнить статический анализ кода. Это забирает много мест, где функция close() не была правильно обработана. У Findbugs есть плагин для Eclipse, но он также работает автономно для одноразовых приложений, имеет интеграцию в Jenkins CI и другие инструменты сборки
-
-
Во время выполнения:
-
Удержание и фиксация
- Если удержатель ResultSet - ResultSet.CLOSE_CURSORS_OVER_COMMIT, тогда ResultSet закрывается при вызове метода Connection.commit(). Это можно установить с помощью Connection.setHoldability() или с помощью перегруженного метода Connection.createStatement().
-
Ведение журнала во время выполнения.
- Поместите в свой код хорошие записи журнала. Они должны быть ясными и понятными, поэтому клиент, персонал поддержки и товарищи по команде могут понять без обучения. Они должны быть краткими и включать печать состояний/внутренних значений ключевых переменных и атрибутов, чтобы вы могли отслеживать логику обработки. Хорошая регистрация важна для отладки приложений, особенно тех, которые были развернуты.
-
Вы можете добавить отладчик JDBC-драйвера в свой проект (для отладки - фактически не развертывайте его). Один пример (я его не использовал) log4jdbc. Затем вам нужно сделать простой анализ этого файла, чтобы увидеть, какие из исполнений не имеют соответствующего закрытия. Подсчет открытых и закрытых должен подчеркнуть, есть ли потенциальная проблема
- Контроль базы данных. Контролируйте запущенное приложение с помощью таких инструментов, как SQL Developer 'Monitor SQL' или Quest TOAD. Мониторинг описан в в этой статье. Во время мониторинга вы запрашиваете открытые курсоры (например, из таблицы v $sesstat) и просматриваете их SQL. Если число курсоров увеличивается, и (что наиболее важно), в котором доминирует один идентичный оператор SQL, вы знаете, что у вас есть утечка с этим SQL. Найдите свой код и просмотрите.
-
Другие мысли
Можете ли вы использовать WeakReferences для обработки закрывающих соединений?
Слабые и мягкие ссылки - это способы, позволяющие вам ссылаться на объект таким образом, чтобы JVM мог мусор собирать референт в любое время, когда он сочтет нужным (при условии, что для этого объекта нет сильных цепей ссылок).
Если вы передаете ReferenceQueue в конструкторе в мягкую или слабую ссылку, объект помещается в ReferenceQueue, когда объект GC'ed, когда он встречается (если он вообще возникает). При таком подходе вы можете взаимодействовать с завершением объекта, и вы можете закрыть или завершить объект в этот момент.
Phantom ссылки немного страннее; их цель состоит только в том, чтобы контролировать завершение, но вы никогда не сможете получить ссылку на исходный объект, поэтому будет сложно вызвать метод close() на нем.
Однако редко бывает хорошей попыткой контролировать, когда выполняется GC (Weak, Soft и PhantomReferences позволяют вам знать после того, как объект был помечен для GC). Фактически, если объем памяти в JVM большой (например, -Xmx2000m), вы никогда не сможете GC объект, и вы все равно будете испытывать ORA-01000. Если память JVM мала по сравнению с вашими требованиями к программе, вы можете обнаружить, что объекты ResultSet и PreparedStatement GCed сразу после создания (прежде чем вы сможете их прочитать), что, скорее всего, не даст вашей программе.
TL; DR: Слабый механизм ссылок не является хорошим способом управления и закрытия объектов Statement и ResultSet.