ORA-01000, помилка максимально відкритих курсорів, є надзвичайно поширеною помилкою в розробці бази даних Oracle. У контексті Java це трапляється, коли програма намагається відкрити більше ResultSets, ніж налаштованих курсорів на екземплярі бази даних.
Поширені причини:
Помилка конфігурації
- У вашій програмі запит на базу даних більше, ніж курсори в БД. Один випадок, коли у вас є з'єднання та пул потоків, більший за кількість курсорів у базі даних.
- У вас є багато розробників або додатків, підключених до одного екземпляра БД (який, ймовірно, включає багато схем), і разом ви використовуєте занадто багато з'єднань.
Рішення:
Витік курсору
- Програми не закривають ResultSets (в JDBC) або курсори (у збережених процедурах в базі даних)
- Рішення : Протікання курсору - це помилки; збільшення кількості курсорів на БД просто затримує неминучий збій. Витоки можна знайти за допомогою статичного аналізу коду , JDBC або журналу на рівні додатків та моніторингу баз даних .
Фон
У цьому розділі описано деякі теорії, що стоять за курсорами, і як слід використовувати JDBC. Якщо вам не потрібно знати тло, ви можете пропустити це та перейти безпосередньо до "Усунення витоків".
Що таке курсор?
Курсор - це ресурс у базі даних, який містить стан запиту, зокрема позицію, де читач знаходиться в ResultSet. Кожен оператор SELECT має курсор, і збережені PL / SQL процедури можуть відкривати та використовувати стільки курсорів, скільки потрібно. Більше про курсори можна дізнатися на Orafaq .
Екземпляр бази даних, як правило, обслуговує декілька різних схем , безліч різних користувачів, які мають кілька сеансів . Для цього у нього є фіксована кількість курсорів, доступних для всіх схем, користувачів та сесій. Коли всі курсори відкриті (використовуються) і запит надходить, що вимагає нового курсору, запит не працює з помилкою ORA-010000.
Пошук та встановлення кількості курсорів
Номер зазвичай налаштовується DBA при встановленні. Кількість курсорів, які зараз використовуються, максимальна кількість та конфігурація можуть бути доступні в функціях адміністратора в Oracle SQL Developer . З SQL його можна встановити за допомогою:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Відношення JDBC в JVM до курсорів у БД
Нижче наведені об'єкти JDBC тісно пов'язані з такими концепціями бази даних:
- JDBC Connection - це представлення клієнтом сеансу бази даних та забезпечує транзакції з базами даних . У з’єднанні може бути відкрита лише одна транзакція в будь-який час (але транзакції можуть бути вкладені)
- JDBC ResultSet підтримується одним курсором бази даних. Коли виклик close () на ResultSet, курсор відпускається.
- JDBC CallableStatement викликає збережену процедуру в базі даних, часто записану в PL / SQL. Збережена процедура може створити нульовий або більше курсорів і може повернути курсор як JDBC ResultSet.
JDBC є безпечним для потоків: цілком нормально передавати різні об'єкти JDBC між потоками.
Наприклад, ви можете створити з'єднання в одному потоці; інший потік може використовувати це з'єднання для створення PreparedStatement, а третій потік може обробити набір результатів. Єдине основне обмеження полягає в тому, що ви не можете будь-коли відкрити більше, ніж один ResultSet на одному Підготовленому стані. Побачити Чи підтримує 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) { }
}
Зауважте, як остаточне зауваження ігнорує будь-який виняток, порушений функцією close ():
- Якщо ви просто закриєте ResultSet без спроби {} catch {}, це може вийти з ладу і не допустити закриття заяви
- Ми хочемо дозволити будь-яке виняток, що виникає в тілі спроби поширитись на абонента. Якщо у вас є цикл, наприклад, створення та виконання заяв, не забудьте закрити кожну заяву в циклі.
У Java 7 Oracle представив інтерфейс AutoCloseable, який замінює більшість котлів Java 6 на симпатичний синтаксичний цукор.
Зберігання об'єктів JDBC
Об'єкти JDBC можна безпечно утримувати в локальних змінних, екземплярах об'єктів та членах класу. Як правило, краща практика:
- Використовуйте об'єкт об'єкта або учасників класу для утримання об'єктів JDBC, які повторно використовуються багато разів протягом більш тривалого періоду, таких як З'єднання та Підготовлені Стани
- Використовуйте локальні змінні для ResultSets, оскільки вони отримані, перекинуті і закриті, як правило, в межах однієї функції.
Однак є один виняток: якщо ви використовуєте EJB або контейнер Servlet / JSP, вам слід дотримуватися суворої моделі нитки:
- Тільки сервер додатків створює потоки (за допомогою яких він обробляє вхідні запити)
- Тільки сервер додатків створює з'єднання (які ви отримуєте з пулу з'єднань)
- Зберігаючи значення (стан) між дзвінками, ви повинні бути дуже обережними. Ніколи не зберігайте значення у власних кешах або статичних членах - це не безпечно для кластерів та інших дивних умов, і сервер додатків може робити страшні речі для ваших даних. Замість цього використовуйте видатні боби або базу даних.
- Зокрема, ніколи не тримайте об’єкти JDBC (Connections, ResultSets, PreparedStatements тощо) над різними віддаленими викликами - нехай сервер програм керує цим. Сервер додатків не тільки забезпечує пул з'єднань, але також кешує ваші PreparedStatements.
Усунення витоків
Існує ряд процесів та інструментів, які допомагають виявити та усунути витоки JDBC:
Під час розвитку - найчастіше підхоплення помилок - це найкращий підхід:
Практики розробки: хороші практики розробки повинні зменшити кількість помилок у вашому програмному забезпеченні, перш ніж він залишає стіл розробника. Конкретні практики включають:
- Парне програмування , щоб навчати тих, хто не має достатнього досвіду
- Код оглядів оскільки багато очей краще, ніж одне
- Блок тестування що означає, що ви можете здійснювати будь-яку кодову базу коду за допомогою тестового інструменту, який робить відтворення витоків тривіальними
- Використовуйте наявні бібліотеки для об'єднання з'єднань, а не для створення власної
Статичний аналіз коду: для аналізу статичного коду використовуйте такий інструмент, як відмінні Findbugs . Це підбирає багато місць, де закриття () не було належним чином оброблено. У Findbugs є плагін для Eclipse, але він також працює окремо для одноразових, має інтеграцію в CI Jenkins та інші інструменти побудови
Під час виконання:
Гідність та відданість
- Якщо вміст ResultSet є ResultSet.CLOSE_CURSORS_OVER_COMMIT, тоді ResultSet закривається, коли викликається метод Connection.commit (). Це можна встановити за допомогою Connection.setHoldability () або за допомогою методу перевантаженого Connection.createStatement ().
Ведення журналу під час виконання.
- Введіть хороші виписки з журналу у свій код. Вони повинні бути чіткими і зрозумілими, щоб клієнт, обслуговуючий персонал та колеги могли зрозуміти без навчання. Вони повинні бути лаконічними і включати друк стану / внутрішніх значень ключових змінних та атрибутів, щоб можна було простежити логіку обробки. Хороший журнал є основним для налагодження програм, особливо тих, які були розгорнуті.
Ви можете додати до свого проекту драйвер JDBC (для налагодження - фактично не розгортайте його). Один із прикладів (я цього не використовував) - log4jdbc . Потім потрібно зробити простий аналіз цього файлу, щоб побачити, які виконавці не мають відповідного закриття. Підрахунок відкритих та закритих має підкреслити, якщо є потенційна проблема
- Моніторинг бази даних. Контролюйте свою запущену програму за допомогою таких інструментів, як функція "Монітор SQL" розробника SQL або Quest's TOAD . Моніторинг описаний у цій статті . Під час моніторингу ви запитуєте відкриті курсори (наприклад, з таблиці v $ sesstat) та переглядаєте їх SQL. Якщо кількість курсорів збільшується і (головне) стає домінуючим одним ідентичним оператором SQL, ви знаєте, що у вас є витік із цим SQL. Шукайте свій код і перегляньте.
Інші думки
Чи можете ви використовувати WeakReferences для обробки з'єднань, що закриваються?
Слабкі та м'які посилання - це спосіб дозволити вам посилатись на об’єкт таким чином, що дозволяє JVM збирати сміття референта в будь-який час, який він вважає за потрібне (якщо припустити, що для цього об’єкта немає сильних ланцюгів відліку).
Якщо ви передаєте ReferenceQueue в конструкторі м'якому або слабкому посиланню, об'єкт розміщується в ReferenceQueue, коли об'єкт GC'ed, коли він виникає (якщо він взагалі виникає). При такому підході ви можете взаємодіяти з доопрацюванням об’єкта, і ви могли закрити або доопрацювати об’єкт у той момент.
Посилання на фантоми дещо дивніші; їх мета полягає лише в управлінні доопрацюванням, але ви ніколи не можете отримати посилання на оригінальний об'єкт, тому назвати на ньому метод close () буде важко.
Однак, дуже рідко є ідея спробувати контролювати, коли запускається GC (слабкі, м'які та PhantomReferences повідомляють вас після того , як об'єкт передається для GC). Насправді, якщо об'єм пам'яті в JVM великий (наприклад, -Xmx2000m), ви ніколи не зможете GC об'єктом, і ви все одно відчуєте ORA-01000. Якщо пам'ять JVM невелика щодо вимог вашої програми, ви можете виявити, що об'єкти ResultSet і PreparedStatement GCed одразу після створення (перш ніж ви зможете прочитати з них), що, ймовірно, не зможе програму.
TL; DR: Слабкий контрольний механізм не є хорошим способом керування та закриття об'єктів Statement та ResultSet.
for (String language : additionalLangs) {