ORA-01000, błąd maksymalnego otwarcia kursorów, jest niezwykle częstym błędem podczas tworzenia baz danych Oracle. W kontekście Javy dzieje się tak, gdy aplikacja próbuje otworzyć więcej zestawów wyników niż jest skonfigurowanych kursorów w instancji bazy danych.
Najczęstsze przyczyny to:
-
Błąd konfiguracji
- W Twojej aplikacji masz więcej wątków, które wysyłają zapytania do bazy danych niż kursorów w bazie danych. Jednym z przypadków jest to, że masz połączenie i pulę wątków większą niż liczba kursorów w bazie danych.
- Masz wielu programistów lub aplikacji podłączonych do tej samej instancji DB (która prawdopodobnie będzie zawierać wiele schematów) i razem używasz zbyt wielu połączeń.
-
Rozwiązanie:
- Zwiększenie liczby kursorów w bazie danych (jeśli pozwalają na to zasoby) lub
- Zmniejszanie liczby wątków w aplikacji.
-
Wyciek kursora
- Aplikacje nie zamykają zestawów wyników (w JDBC) ani kursorów (w procedurach składowanych w bazie danych)
- Rozwiązanie :Wycieki kursora to błędy; zwiększenie liczby kursorów w DB po prostu opóźnia nieuniknioną awarię. Wycieki można znaleźć za pomocą statycznej analizy kodu, rejestrowania JDBC lub na poziomie aplikacji oraz monitorowania bazy danych.
Tło
Ta sekcja opisuje niektóre z teorii kryjących się za kursorami i jak należy używać JDBC. Jeśli nie musisz znać tła, możesz to pominąć i przejść od razu do „Eliminacji przecieków”.
Co to jest kursor?
Kursor to zasób w bazie danych, który przechowuje stan zapytania, w szczególności pozycję, w której znajduje się czytnik w zestawie wyników. Każda instrukcja SELECT ma kursor, a procedury składowane PL/SQL mogą otwierać i używać tyle kursorów, ile wymagają. Możesz dowiedzieć się więcej o kursorach w Orafaq.
Instancja bazy danych zazwyczaj obsługuje kilka różnych schematów , wielu różnych użytkowników każdy z wieloma sesjami . Aby to zrobić, ma stałą liczbę kursorów dostępnych dla wszystkich schematów, użytkowników i sesji. Gdy wszystkie kursory są otwarte (w użyciu) i pojawia się żądanie wymagające nowego kursora, żądanie kończy się niepowodzeniem z błędem ORA-010000.
Znajdowanie i ustawianie liczby kursorów
Numer jest zwykle konfigurowany przez administratora baz danych podczas instalacji. Liczbę aktualnie używanych kursorów, maksymalną liczbę i konfigurację można uzyskać w funkcjach administratora w Oracle SQL Developer. Z SQL można to ustawić za pomocą:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Powiązanie JDBC w JVM z kursorami w bazie danych
Poniższe obiekty JDBC są ściśle powiązane z następującymi koncepcjami baz danych:
- JDBC Połączenie jest reprezentacją klienta bazy danych sesja i zapewnia bazy danych transakcje . Połączenie może mieć otwartą tylko jedną transakcję w danym momencie (ale transakcje mogą być zagnieżdżone)
- JDBC Zestaw wyników jest obsługiwany przez jeden kursor w bazie danych. Kiedy close() jest wywoływana w zestawie wyników, kursor jest zwalniany.
- JDBC CallableStatement wywołuje procedurę zapisaną w bazie danych, często pisanych w PL/SQL. Procedura składowana może utworzyć zero lub więcej kursorów i może zwrócić kursor jako zestaw wyników JDBC.
JDBC jest bezpieczny dla wątków:przekazywanie różnych obiektów JDBC między wątkami jest całkiem w porządku.
Na przykład możesz utworzyć połączenie w jednym wątku; inny wątek może użyć tego połączenia do utworzenia PreparedStatement, a trzeci wątek może przetworzyć zestaw wyników. Jedynym głównym ograniczeniem jest to, że w jednym Przygotowanym Oświadczeniu nie można mieć otwartych więcej niż jednego Zestawu Wyników w dowolnym momencie. Zobacz Czy Oracle DB obsługuje wiele (równoległych) operacji na połączenie?
Zauważ, że zatwierdzenie bazy danych następuje w połączeniu, więc wszystkie DML (INSERT, UPDATE i DELETE) w tym połączeniu zostaną zatwierdzone razem. Dlatego, jeśli chcesz obsługiwać wiele transakcji jednocześnie, musisz mieć co najmniej jedno Połączenie dla każdej równoczesnej Transakcji.
Zamykanie obiektów JDBC
Typowym przykładem wykonania zestawu wyników jest:
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) { }
}
Zwróć uwagę, że klauzula finally ignoruje wszelkie wyjątki zgłoszone przez close():
- Jeśli po prostu zamkniesz zestaw wyników bez try {} catch {}, może się to nie udać i uniemożliwić zamknięcie instrukcji
- Chcemy zezwolić każdemu wyjątkowi zgłoszonemu w treści próby na propagację do wywołującego. Jeśli masz pętlę, na przykład podczas tworzenia i wykonywania instrukcji, pamiętaj o zamknięciu każdej instrukcji w pętli.
W Java 7 firma Oracle wprowadziła interfejs AutoCloseable, który zastępuje większość schematów Java 6 ładnym cukrem składniowym.
Przechowywanie obiektów JDBC
Obiekty JDBC można bezpiecznie przechowywać w zmiennych lokalnych, instancjach obiektów i członkach klasy. Generalnie lepszą praktyką jest:
- Użyj instancji obiektu lub elementów klasy do przechowywania obiektów JDBC, które są wielokrotnie używane przez dłuższy czas, takich jak Connections i PreparedStatements
- Używaj zmiennych lokalnych dla zestawów wyników, ponieważ są one uzyskiwane, zapętlane, a następnie zamykane zazwyczaj w zakresie pojedynczej funkcji.
Jest jednak jeden wyjątek:jeśli używasz EJB lub kontenera Servlet/JSP, musisz przestrzegać ścisłego modelu wątków:
- Tylko serwer aplikacji tworzy wątki (za pomocą których obsługuje przychodzące żądania)
- Tylko serwer aplikacji tworzy połączenia (które uzyskujesz z puli połączeń)
- Podczas zapisywania wartości (stanu) między połączeniami musisz być bardzo ostrożny. Nigdy nie przechowuj wartości we własnych pamięciach podręcznych ani w statycznych elementach członkowskich — nie jest to bezpieczne w klastrach i innych dziwnych warunkach, a serwer aplikacji może zrobić straszne rzeczy z twoimi danymi. Zamiast tego użyj stanowych ziaren lub bazy danych.
- W szczególności nigdy przechowuj obiekty JDBC (Connections, ResultsSets, PreparedStatements, itp.) nad różnymi zdalnymi wywołaniami — pozwól, aby zarządzał nimi serwer aplikacji. Serwer aplikacji nie tylko zapewnia pulę połączeń, ale także buforuje Twoje przygotowane oświadczenia.
Eliminacja wycieków
Dostępnych jest wiele procesów i narzędzi, które pomagają wykrywać i eliminować wycieki JDBC:
-
Podczas opracowywania - wczesne wykrywanie błędów jest zdecydowanie najlepszym podejściem:
-
Praktyki programistyczne:Dobre praktyki programistyczne powinny zmniejszyć liczbę błędów w oprogramowaniu, zanim opuści ono biurko programisty. Konkretne praktyki obejmują:
- Paruj programowanie, aby edukować osoby bez wystarczającego doświadczenia
- Recenzje kodu, ponieważ wiele oczu jest lepszych niż jedno
- Testowanie jednostkowe, co oznacza, że możesz ćwiczyć dowolną bazę kodu z narzędzia testowego, co sprawia, że odtwarzanie wycieków jest trywialne
- Użyj istniejących bibliotek do puli połączeń zamiast tworzyć własne
-
Statyczna analiza kodu:Użyj narzędzia, takiego jak doskonałe Findbugs, aby przeprowadzić statyczną analizę kodu. To wykrywa wiele miejsc, w których close() nie została poprawnie obsłużona. Findbugs ma wtyczkę do Eclipse, ale działa również jako samodzielna funkcja jednorazowa, ma integrację z Jenkins CI i innymi narzędziami do budowania
-
-
W czasie wykonywania:
-
Przechowalność i zaangażowanie
- Jeśli zdolność przechowywania zestawu wyników to zestaw wyników.CLOSE_CURSORS_OVER_COMMIT, to zestaw wyników jest zamykany po wywołaniu metody Connection.commit(). Można to ustawić za pomocą metody Connection.setHoldability() lub przeciążonej metody Connection.createStatement().
-
Logowanie w czasie wykonywania.
- Umieść w kodzie prawidłowe instrukcje dziennika. Powinny one być jasne i zrozumiałe, aby klient, personel pomocniczy i koledzy z zespołu mogli zrozumieć bez szkolenia. Powinny być zwięzłe i obejmować drukowanie stanu/wewnętrznych wartości zmiennych kluczowych i atrybutów, aby można było śledzić logikę przetwarzania. Dobre rejestrowanie jest podstawą debugowania aplikacji, zwłaszcza tych, które zostały wdrożone.
-
Możesz dodać debugujący sterownik JDBC do swojego projektu (w celu debugowania — nie wdrażaj go w rzeczywistości). Jednym z przykładów (nie korzystałem z niego) jest log4jdbc. Następnie musisz przeprowadzić prostą analizę tego pliku, aby zobaczyć, które wykonania nie mają odpowiedniego zamknięcia. Liczenie otwarć i zamknięć powinno być podświetlone, jeśli istnieje potencjalny problem
- Monitorowanie bazy danych. Monitoruj uruchomioną aplikację za pomocą narzędzi, takich jak funkcja „Monitoruj SQL” programu SQL Developer lub TOAD firmy Quest. Monitorowanie zostało opisane w tym artykule. Podczas monitorowania odpytujesz otwarte kursory (np. z tabeli v$sesstat) i przeglądasz ich SQL. Jeśli liczba kursorów rośnie i (co najważniejsze) zostaje zdominowana przez jedno identyczne polecenie SQL, wiesz, że masz przeciek z tym kodem SQL. Wyszukaj swój kod i sprawdź.
-
Inne przemyślenia
Czy możesz użyć WeakReferences do obsługi zamykania połączeń?
Słabe i miękkie referencje to sposoby na odwoływanie się do obiektu w sposób, który pozwala maszynie JVM na zbieranie śmieci w dowolnym momencie, który uzna za odpowiedni (zakładając, że nie ma silnych łańcuchów referencji do tego obiektu).
Jeśli przekażesz ReferenceQueue w konstruktorze do miękkiej lub słabej Reference, obiekt zostanie umieszczony w ReferenceQueue, gdy obiekt zostanie poddany GC, gdy wystąpi (jeśli w ogóle wystąpi). Dzięki takiemu podejściu możesz wchodzić w interakcje z finalizacją obiektu i możesz w tym momencie zamknąć lub sfinalizować obiekt.
Odniesienia fantomowe są nieco dziwniejsze; ich celem jest tylko kontrola finalizacji, ale nigdy nie można uzyskać odniesienia do oryginalnego obiektu, więc wywołanie na nim metody close() będzie trudne.
Jednak rzadko jest dobrym pomysłem, aby próbować kontrolować, kiedy GC jest uruchamiany (słabe, miękkie i fantomowe referencje informują Cię po fakcie że obiekt jest umieszczony w kolejce dla GC). W rzeczywistości, jeśli ilość pamięci w JVM jest duża (np. -Xmx2000m), możesz nigdy GC obiekt, a nadal będziesz doświadczać ORA-01000. Jeśli pamięć JVM jest mała w stosunku do wymagań twojego programu, może się okazać, że obiekty ResultSet i PreparedStatement są GCed natychmiast po utworzeniu (zanim będziesz mógł z nich czytać), co prawdopodobnie zawiedzie twój program.
TL;DR: Mechanizm słabego odniesienia nie jest dobrym sposobem na zarządzanie i zamykanie obiektów Statement i ResultSet.