ORA-01000, ошибка максимального количества открытых курсоров, является чрезвычайно распространенной ошибкой при разработке баз данных Oracle. В контексте Java это происходит, когда приложение пытается открыть больше ResultSets, чем настроено курсоров в экземпляре базы данных.
Общие причины:
Ошибка конфигурации
- В вашем приложении больше потоков, запрашивающих базу данных, чем курсоров в БД. В одном случае количество соединений и пул потоков превышает количество курсоров в базе данных.
- У вас есть много разработчиков или приложений, подключенных к одному экземпляру БД (который, вероятно, будет включать много схем), и вместе вы используете слишком много подключений.
Решение:
Утечка курсора
- Приложения не закрывают 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 с курсорами в БД
Приведенные ниже объекты JDBC тесно связаны со следующими концепциями базы данных:
- JDBC Connection - это клиентское представление сеанса базы данных и обеспечивает транзакции с базой данных . Соединение может иметь только одну открытую транзакцию одновременно (но транзакции могут быть вложенными)
- JDBC ResultSet поддерживается одним курсором на базе данных. Когда для ResultSet вызывается close (), курсор отпускается.
- JDBC CallableStatement вызывает хранимую процедуру в базе данных, часто написанную на PL / SQL. Хранимая процедура может создавать ноль или более курсоров и может возвращать курсор в виде набора результатов JDBC.
JDBC является потокобезопасным: вполне нормально передавать различные объекты JDBC между потоками.
Например, вы можете создать соединение в одном потоке; другой поток может использовать это соединение для создания PreparedStatement, а третий поток может обработать набор результатов. Единственное серьезное ограничение заключается в том, что вы не можете одновременно открывать более одного ResultSet на одном PreparedStatement. См. Поддерживает ли база данных Oracle несколько (параллельных) операций на одно соединение?
Обратите внимание, что фиксация базы данных происходит в соединении, и поэтому все 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 {}, он может выйти из строя и помешать закрытию оператора
- Мы хотим, чтобы любое исключение, возникшее в теле попытки, передавалось вызывающему. Если у вас есть цикл, например создание и выполнение операторов, не забудьте закрыть каждый оператор внутри цикла.
В Java 7 Oracle представила интерфейс AutoCloseable, который заменяет большую часть шаблонного кода Java 6 некоторым приятным синтаксическим сахаром.
Хранение объектов JDBC
Объекты JDBC можно безопасно хранить в локальных переменных, экземплярах объекта и членах класса. Как правило, лучше:
- Используйте экземпляр объекта или члены класса для хранения объектов JDBC, которые повторно используются несколько раз в течение более длительного периода, например Connections и PreparedStatements.
- Используйте локальные переменные для ResultSets, поскольку они обычно получаются, зацикливаются и затем закрываются в рамках одной функции.
Однако есть одно исключение: если вы используете EJB или контейнер сервлетов / JSP, вы должны следовать строгой модели потоковой передачи:
- Только сервер приложений создает потоки (с которыми он обрабатывает входящие запросы)
- Только сервер приложений создает соединения (которые вы получаете из пула соединений)
- При сохранении значений (состояния) между вызовами нужно быть очень осторожным. Никогда не храните значения в ваших собственных кэшах или статических элементах - это небезопасно для кластеров и других странных условий, а сервер приложений может творить ужасные вещи с вашими данными. Вместо этого используйте компоненты с отслеживанием состояния или базу данных.
- В частности, никогда не удерживайте объекты JDBC (Connections, ResultSets, PreparedStatements и т. Д.) При различных удаленных вызовах - позвольте серверу приложений управлять этим. Сервер приложений не только предоставляет пул соединений, но и кэширует ваши 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's TOAD . Мониторинг описан в этой статье . Во время мониторинга вы запрашиваете открытые курсоры (например, из таблицы v $ sesstat) и просматриваете их SQL. Если количество курсоров увеличивается и (что наиболее важно) преобладает один идентичный оператор SQL, вы знаете, что у вас есть утечка с этим SQL. Найдите свой код и просмотрите.
Другие мысли
Можете ли вы использовать WeakReferences для обработки закрывающихся соединений?
Слабые и мягкие ссылки - это способы, позволяющие вам ссылаться на объект таким образом, который позволяет JVM собирать мусор для референта в любое время, которое она сочтет нужным (при условии, что на этот объект нет сильных цепочек ссылок).
Если вы передаете ReferenceQueue в конструкторе мягкой или слабой ссылке, объект помещается в ReferenceQueue, когда объект GC'ed, когда он возникает (если это происходит вообще). При таком подходе вы можете взаимодействовать с финализацией объекта, и вы можете закрыть или финализировать объект в этот момент.
Фантомные ссылки немного страннее; их цель - только контролировать финализацию, но вы никогда не сможете получить ссылку на исходный объект, поэтому будет сложно вызвать для него метод close ().
Однако попытка управления запуском GC редко бывает хорошей идеей (Weak, Soft и PhantomReferences сообщают вам после того , как объект поставлен в очередь для GC). Фактически, если объем памяти в JVM велик (например, -Xmx2000m), вы можете никогда не выполнить сборку мусора для объекта, но все равно столкнетесь с ORA-01000. Если память JVM мала по сравнению с требованиями вашей программы, вы можете обнаружить, что объекты ResultSet и PreparedStatement собираются сразу после создания (до того, как вы сможете их читать), что, скорее всего, приведет к сбою вашей программы.
TL; DR: слабый ссылочный механизм - не лучший способ управлять и закрывать объекты Statement и ResultSet.
for (String language : additionalLangs) {