ORA-01000, błąd maksymalnej liczby otwartych kursorów, jest niezwykle częstym błędem w rozwoju bazy danych Oracle. W kontekście języka Java dzieje się tak, gdy aplikacja próbuje otworzyć więcej zestawów wyników niż skonfigurowanych kursorów w instancji bazy danych.
Typowe przyczyny to:
Błąd konfiguracji
- W aplikacji masz więcej wątków wykonujących zapytania do bazy danych niż kursorów w bazie danych. W jednym przypadku 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 bazy danych (która prawdopodobnie będzie zawierać wiele schematów) i razem używasz zbyt wielu połączeń.
Rozwiązanie:
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 bazie danych po prostu opóźnia nieuniknioną awarię. Wycieki można znaleźć za pomocą statycznej analizy kodu , JDBC lub rejestrowania na poziomie aplikacji oraz monitorowania bazy danych .
tło
W tej sekcji opisano niektóre teorie stojące za kursorami i sposób korzystania z JDBC. Jeśli nie musisz znać tła, możesz to pominąć i przejść od razu do „Eliminacji wyciekó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 czytnik znajduje się w zestawie wyników. Każda instrukcja SELECT ma kursor, a procedury składowane PL / SQL mogą otwierać i używać dowolnej liczby kursorów. Możesz dowiedzieć się więcej o kursorach na Orafaq .
Instancja bazy danych zazwyczaj obsługuje kilka różnych schematów , wielu różnych użytkowników, z których każdy ma wiele sesji . 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 przychodzi żą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 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 bazy danych:
- Połączenie JDBC jest reprezentacją klienta sesji bazy danych i zapewnia transakcje bazy danych . Połączenie może mieć otwartą tylko jedną transakcję w danym momencie (ale transakcje mogą być zagnieżdżane)
- JDBC wynikowa jest obsługiwany przez jedną kursora w bazie danych. Gdy metoda close () jest wywoływana w zestawie wyników, kursor jest zwalniany.
- JDBC CallableStatement wywołuje procedurę składowaną w bazie danych, często napisaną w języku 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 dowolnym momencie nie można mieć otwartych więcej niż jednego zestawu wyników na jednym przygotowanym zestawieniu. Zobacz Czy baza danych Oracle obsługuje wiele (równoległych) operacji na połączenie?
Zauważ, że zatwierdzenie bazy danych występuje w połączeniu, a więc wszystkie DML (INSERT, UPDATE i DELETE) w tym połączeniu zostaną zatwierdzone razem. Dlatego jeśli chcesz obsługiwać wiele transakcji w tym samym czasie, musisz mieć co najmniej jedno połączenie dla każdej współbieżnej transakcji.
Zamykanie obiektów JDBC
Typowy przykład wykonania zestawu wyników to:
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ę, jak klauzula last ignoruje każdy wyjątek wywołany przez funkcję close ():
- Jeśli po prostu zamkniesz zestaw wyników bez try {} catch {}, może to się nie powieść i uniemożliwić zamknięcie instrukcji
- Chcemy, aby każdy wyjątek zgłoszony w treści próby był propagowany do wywołującego. Jeśli masz pętlę, na przykład podczas tworzenia i wykonywania instrukcji, pamiętaj, aby zamknąć każdą instrukcję w pętli.
W Javie 7 firma Oracle wprowadziła interfejs AutoCloseable, który zastępuje większość standardowego schematu Java 6 jakimś ładnym cukrem składniowym.
Trzymanie obiektów JDBC
Obiekty JDBC można bezpiecznie przechowywać w zmiennych lokalnych, instancji obiektu i elementach klas. Generalnie lepszą praktyką jest:
- Użyj instancji obiektu lub członków klasy, aby przechowywać obiekty JDBC, które są wielokrotnie używane przez dłuższy czas, takie jak Connections i PreparedStatements
- Użyj zmiennych lokalnych dla zestawów wyników, ponieważ są one uzyskiwane, zapętlane, a następnie zamykane zwykle 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 trzeba być bardzo ostrożnym. Nigdy nie przechowuj wartości we własnych pamięciach podręcznych ani statycznych elementach członkowskich - nie jest to bezpieczne w przypadku klastrów i innych dziwnych warunków, a serwer aplikacji może wyrządzić straszne rzeczy z Twoimi danymi. Zamiast tego użyj stanowych fasoli lub bazy danych.
- W szczególności nigdy nie zatrzymuj obiektów JDBC (Connections, ResultSets, PreparedStatements itp.) Nad różnymi zdalnymi wywołaniami - pozwól, aby zarządzał tym serwer aplikacji. Serwer aplikacji nie tylko udostępnia pulę połączeń, ale także buforuje Twoje PreparedStatements.
Eliminacja wycieków
Dostępnych jest wiele procesów i narzędzi ułatwiających wykrywanie i eliminowanie wycieków JDBC:
Podczas programowania - wczesne wychwytywanie błędów jest zdecydowanie najlepszym podejściem:
Praktyki programistyczne: Dobre praktyki programistyczne powinny zmniejszyć liczbę błędów w oprogramowaniu, zanim opuści biurko programisty. Konkretne praktyki obejmują:
- Programowanie w parach, aby kształcić osoby bez wystarczającego doświadczenia
- Recenzje kodu, ponieważ wiele oczu jest lepszych niż jedno
- Testowanie jednostkowe, co oznacza, że możesz przetestować całą bazę kodu za pomocą narzędzia testowego, co sprawia, że odtwarzanie wycieków jest banalne
- Użyj istniejących bibliotek do tworzenia puli połączeń, zamiast tworzyć własne
Statyczna analiza kodu: użyj narzędzia takiego jak doskonałe Findbugs, aby przeprowadzić statyczną analizę kodu. Wybiera to wiele miejsc, w których metoda close () nie została poprawnie obsłużona. Findbugs ma wtyczkę do Eclipse, ale działa również samodzielnie w przypadku jednorazowych czynności, ma integrację z Jenkins CI i innymi narzędziami do kompilacji
W czasie wykonywania:
Zachowalność i zobowiązanie
- Jeśli możliwość przechowywania zestawu wyników to ResultSet.CLOSE_CURSORS_OVER_COMMIT, zestaw wyników jest zamykany po wywołaniu metody Connection.commit (). Można to ustawić za pomocą Connection.setHoldability () lub przeciążonej metody Connection.createStatement ().
Rejestrowanie w czasie wykonywania.
- Umieść dobre instrukcje dziennika w swoim kodzie. Powinny one być jasne i zrozumiałe, aby klient, personel pomocniczy i członkowie zespołu mogli zrozumieć bez szkolenia. Powinny być zwięzłe i obejmować drukowanie stanu / wartości wewnętrznych kluczowych zmiennych i atrybutów, tak 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 (do debugowania - nie wdrażaj go). Jednym z przykładów (nie używałem go) jest log4jdbc . Następnie musisz wykonać prostą analizę tego pliku, aby zobaczyć, które pliki wykonywalne nie mają odpowiedniego zamknięcia. Liczenie otwarcia i zamknięcia powinno pokazać, czy istnieje potencjalny problem
- Monitorowanie bazy danych. Monitoruj uruchomioną aplikację za pomocą narzędzi, takich jak funkcja „Monitor SQL” programisty SQL lub TOAD firmy Quest . Monitorowanie opisano 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) jest zdominowana przez jedną identyczną instrukcję SQL, wiesz, że masz przeciek z tym kodem SQL. Przeszukaj swój kod i przejrzyj.
Inne przemyślenia
Czy możesz używać WeakReferences do obsługi zamykania połączeń?
Słabe i miękkie referencje to sposoby na umożliwienie odniesienia do obiektu w sposób, który pozwala JVM zebrać odwołanie do pamięci w dowolnym momencie, który uzna za stosowny (zakładając, że nie ma silnych łańcuchów odwołań do tego obiektu).
Jeśli przekażesz ReferenceQueue w konstruktorze do miękkiego lub słabego 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 iw tym momencie możesz zamknąć lub sfinalizować obiekt.
Odniesienia do fantomów są nieco dziwniejsze; ich celem jest tylko kontrolowanie finalizacji, ale nigdy nie można uzyskać odniesienia do oryginalnego obiektu, więc trudno będzie wywołać na nim metodę close ().
Jednak rzadko dobrym pomysłem jest próba kontrolowania, kiedy GC jest uruchamiany (Weak, Soft i PhantomReferences informują o tym, że obiekt jest umieszczony w kolejce do GC). W rzeczywistości, jeśli ilość pamięci w JVM jest duża (np. -Xmx2000m), możesz nigdy nie wykonać GC obiektu i nadal będziesz korzystać z ORA-01000. Jeśli pamięć maszyny JVM jest mała w stosunku do wymagań programu, może się okazać, że obiekty ResultSet i PreparedStatement są poddawane GC natychmiast po utworzeniu (zanim będzie można z nich odczytać), co prawdopodobnie zakończy się niepowodzeniem.
TL; DR: słaby mechanizm odniesienia nie jest dobrym sposobem na zarządzanie i zamykanie obiektów Statement i ResultSet.
for (String language : additionalLangs) {