sql >> Databáze >  >> RDS >> Oracle

java.sql.SQLException:- ORA-01000:překročen maximální počet otevřených kurzorů

ORA-01000, chyba maximum-open-cursors, je extrémně častá chyba při vývoji databáze Oracle. V kontextu Javy k tomu dochází, když se aplikace pokusí otevřít více ResultSet, než kolik je nakonfigurovaných kurzorů na instanci databáze.

Běžné příčiny jsou:

  1. Chyba konfigurace

    • Ve své aplikaci máte více vláken dotazujících se na databázi než kurzorů v DB. Jedním z případů je situace, kdy máte připojení a fond vláken větší, než je počet kurzorů v databázi.
    • Máte mnoho vývojářů nebo aplikací připojených ke stejné instanci DB (která bude pravděpodobně zahrnovat mnoho schémat) a společně používáte příliš mnoho připojení.
    • Řešení:

      • Zvýšení počtu kurzorů v databázi (pokud to prostředky umožňují) nebo
      • Snížení počtu vláken v aplikaci.
  2. Únik kurzoru

    • Aplikace neuzavírají ResultSets (v JDBC) nebo kurzory (v uložených procedurách v databázi)
    • Řešení :Úniky kurzoru jsou chyby; zvýšení počtu kurzorů na DB jednoduše oddálí nevyhnutelné selhání. Úniky lze nalézt pomocí statické analýzy kódu, protokolování JDBC nebo na úrovni aplikace a monitorování databáze.

Pozadí

Tato část popisuje některé teorie za kurzory a způsob použití JDBC. Pokud nepotřebujete znát pozadí, můžete toto přeskočit a přejít přímo na 'Elimination Leaks'.

Co je to kurzor?

Kurzor je zdroj v databázi, který uchovává stav dotazu, konkrétně pozici, kde je čtečka v sadě výsledků. Každý příkaz SELECT má kurzor a uložené procedury PL/SQL mohou otevřít a použít tolik kurzorů, kolik potřebují. Více o kurzorech se můžete dozvědět na Orafaq.

Instance databáze obvykle obsluhuje několik různých schémat , mnoho různých uživatelů každý s více návštěvami . K tomu má k dispozici pevný počet kurzorů pro všechna schémata, uživatele a relace. Když jsou všechny kurzory otevřené (používají se) a přijde požadavek, který vyžaduje nový kurzor, požadavek selže s chybou ORA-010000.

Nalezení a nastavení počtu kurzorů

Číslo je obvykle konfigurováno správcem databáze při instalaci. Počet aktuálně používaných kurzorů, maximální počet a konfigurace jsou dostupné ve funkcích správce v aplikaci Oracle SQL Developer. Z SQL jej lze nastavit pomocí:

ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;

Vztah JDBC v JVM ke kurzorům v DB

Níže uvedené objekty JDBC jsou úzce spojeny s následujícími koncepty databáze:

  • JDBC Připojení je klientská reprezentace databáze relace a poskytuje databázové transakce . Připojení může mít v jednu chvíli otevřenou pouze jednu transakci (ale transakce mohou být vnořené)
  • JDBC Sada výsledků je podporováno jediným kurzorem na databázi. Když je na ResultSet zavoláno close(), kurzor se uvolní.
  • JDBC CallableStatement vyvolá uloženou proceduru na databázi, často psaný v PL/SQL. Uložená procedura může vytvořit nula nebo více kurzorů a může vrátit kurzor jako sadu výsledků JDBC.

JDBC je bezpečné pro vlákna:Je zcela v pořádku předávat různé objekty JDBC mezi vlákny.

Například můžete vytvořit připojení v jednom vláknu; jiné vlákno může použít toto připojení k vytvoření PreparedStatement a třetí vlákno může zpracovat sadu výsledků. Jediným hlavním omezením je, že nemůžete mít kdykoli otevřeno více než jednu sadu ResultSet na jednom PreparedStatement. Viz Podporuje Oracle DB více (paralelních) operací na připojení?

Všimněte si, že k potvrzení databáze dochází u připojení, a tak se všechny DML (INSERT, UPDATE a DELETE) na tomto připojení potvrdí společně. Pokud tedy chcete podporovat více transakcí současně, musíte mít pro každou souběžnou transakci alespoň jedno připojení.

Zavření objektů JDBC

Typický příklad spuštění ResultSet je:

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) { }
}

Všimněte si, jak klauzule final ignoruje jakoukoli výjimku vyvolanou funkcí close():

  • Pokud jednoduše zavřete sadu výsledků bez pokusu {} catch {}, může selhat a zabránit uzavření příkazu
  • Chceme umožnit, aby se jakákoli výjimka vyvolaná v těle pokusu rozšířila na volajícího. Pokud máte smyčku, například vytváření a provádění příkazů, nezapomeňte zavřít každý příkaz v rámci smyčky.

V Javě 7 společnost Oracle představila rozhraní AutoCloseable, které nahrazuje většinu standardu Java 6 nějakým pěkným syntaktickým cukrem.

Držení objektů JDBC

Objekty JDBC lze bezpečně uchovávat v místních proměnných, instancích objektu a členech třídy. Obecně je lepší praxe:

  • Použijte instanci objektu nebo členy třídy k uložení objektů JDBC, které jsou opakovaně použity vícekrát po delší dobu, jako jsou Connections a PreparedStatements
  • Používejte místní proměnné pro ResultSets, protože se získávají, opakují a poté uzavírají typicky v rámci jedné funkce.

Existuje však jedna výjimka:Pokud používáte EJB nebo kontejner Servlet/JSP, musíte se řídit přísným modelem vláken:

  • Pouze aplikační server vytváří vlákna (se kterými zpracovává příchozí požadavky)
  • Pouze aplikační server vytváří připojení (která získáte z fondu připojení)
  • Při ukládání hodnot (stavu) mezi hovory musíte být velmi opatrní. Nikdy neukládejte hodnoty do svých vlastních mezipamětí nebo statických členů – to není bezpečné napříč clustery a jinými podivnými podmínkami a aplikační server může s vašimi daty dělat hrozné věci. Místo toho použijte stavové fazole nebo databázi.
  • Především nikdy držet objekty JDBC (Connections, ResultSets, PreparedStatements, atd.) přes různá vzdálená vyvolání – nechejte to spravovat aplikačním serverem. Aplikační server poskytuje nejen fond připojení, ale také ukládá vaše PreparedStatements.

Odstranění úniků

Existuje celá řada dostupných procesů a nástrojů, které pomáhají detekovat a eliminovat úniky JDBC:

  1. Během vývoje – včasné zachycení chyb je zdaleka nejlepší přístup:

    1. Vývojové postupy:Dobré vývojové postupy by měly snížit počet chyb ve vašem softwaru, než opustí vývojářský stůl. Mezi konkrétní postupy patří:

      1. Párové programování za účelem vzdělávání lidí bez dostatečných zkušeností
      2. Recenze kódu, protože mnoho očí je lepších než jedno
      3. Testování jednotek, což znamená, že můžete použít jakýkoli a celý svůj kódový základ pomocí testovacího nástroje, díky kterému je reprodukování úniků triviální
      4. Používejte stávající knihovny pro sdružování připojení místo vytváření vlastních
    2. Statická analýza kódu:Použijte nástroj jako vynikající Findbugs k provedení statické analýzy kódu. To zachytí mnoho míst, kde close() nebylo správně zpracováno. Findbugs má plugin pro Eclipse, ale běží také samostatně pro jednorázové použití, má integraci do Jenkins CI a dalších nástrojů pro vytváření

  2. Za běhu:

    1. Držitelnost a závazek

      1. Pokud je udržitelnost ResultSet nastavena na ResultSet.CLOSE_CURSORS_OVER_COMMIT, pak se ResultSet při volání metody Connection.commit() zavře. To lze nastavit pomocí Connection.setHoldability() nebo pomocí přetížené metody Connection.createStatement().
    2. Protokolování za běhu.

      1. Vložte do kódu správné protokoly. Ty by měly být jasné a srozumitelné, aby je zákazník, pracovníci podpory a spoluhráči pochopili i bez školení. Měly by být stručné a zahrnovat tisk stavu/vnitřních hodnot klíčových proměnných a atributů, abyste mohli sledovat logiku zpracování. Dobré protokolování je základem pro ladění aplikací, zejména těch, které byly nasazeny.
      2. Do svého projektu můžete přidat ladicí ovladač JDBC (pro ladění – ve skutečnosti jej nenasazujte). Jedním příkladem (nepoužil jsem ho) je log4jdbc. Poté musíte provést jednoduchou analýzu tohoto souboru, abyste zjistili, která provedení nemají odpovídající uzavření. Počítání otevření a zavření by mělo upozornit na potenciální problém

        1. Monitorování databáze. Monitorujte svou běžící aplikaci pomocí nástrojů, jako je funkce SQL Developer 'Monitor SQL' nebo Quest's TOAD. Monitorování je popsáno v tomto článku. Během monitorování se dotazujete na otevřené kurzory (např. z tabulky v$sesstat) a kontrolujete jejich SQL. Pokud se počet kurzorů zvyšuje a (co je nejdůležitější) začíná dominovat jeden identický příkaz SQL, víte, že u tohoto SQL došlo k úniku informací. Vyhledejte svůj kód a zkontrolujte jej.

Jiné myšlenky

Můžete použít WeakReferences k uzavírání připojení?

Slabé a měkké reference jsou způsoby, jak vám umožnit odkazovat na objekt způsobem, který umožňuje JVM shromažďovat referent, kdykoli to považuje za vhodné (za předpokladu, že k tomuto objektu neexistují žádné silné referenční řetězce).

Pokud předáte ReferenceQueue v konstruktoru měkké nebo slabé referenci, bude objekt umístěn do ReferenceQueue, když je objekt GC'ed, když k němu dojde (pokud se vůbec vyskytne). S tímto přístupem můžete interagovat s finalizací objektu a v tu chvíli můžete objekt zavřít nebo finalizovat.

Fantomové odkazy jsou o něco podivnější; jejich účelem je pouze řídit finalizaci, ale nikdy nemůžete získat odkaz na původní objekt, takže na něj bude těžké volat metodu close().

Zřídkakdy je však dobrý nápad pokoušet se ovládat, kdy je GC spuštěn (Weak, Soft a PhantomReference vám dají vědět potom že objekt je zařazen do fronty pro GC). Ve skutečnosti, pokud je množství paměti v JVM velké (např. -Xmx2000m), nemusíte nikdy GC objekt a stále zažijete ORA-01000. Pokud je paměť JVM vzhledem k požadavkům vašeho programu malá, můžete zjistit, že objekty ResultSet a PreparedStatement jsou GCed ihned po vytvoření (než z nich budete moci číst), což pravděpodobně selže váš program.

TL;DR: Slabý referenční mechanismus není dobrý způsob, jak spravovat a zavírat objekty Statement a ResultSet.



  1. Aktualizace pole JSON nepřetrvávají v databázi

  2. Výkonnostní překvapení a předpoklady:GROUP BY vs. DISTINCT

  3. Je možné použít MySql uživatelem definovanou proměnnou v .NET MySqlCommand?

  4. Jak získat skript tabulky v Oracle SQL Developer?