ORA-01000, maksimum-åben-markør-fejlen, er en ekstremt almindelig fejl i Oracle-databaseudvikling. I forbindelse med Java sker det, når applikationen forsøger at åbne flere resultatsæt, end der er konfigurerede markører på en databaseinstans.
Almindelige årsager er:
-
Konfigurationsfejl
- Du har flere tråde i din applikation, der forespørger databasen end markører på DB. Et tilfælde er, hvor du har en forbindelse og trådpulje, der er større end antallet af markører i databasen.
- Du har mange udviklere eller applikationer forbundet til den samme DB-instans (som sandsynligvis vil omfatte mange skemaer), og sammen bruger du for mange forbindelser.
-
Løsning:
- Forøgelse af antallet af markører på databasen (hvis ressourcer tillader det) eller
- Reduktion af antallet af tråde i applikationen.
-
Markørlækage
- Applikationerne lukker ikke ResultSets (i JDBC) eller markører (i lagrede procedurer i databasen)
- Løsning :Markørlækager er fejl; at øge antallet af markører på DB'en forsinker simpelthen den uundgåelige fejl. Lækager kan findes ved hjælp af statisk kodeanalyse, JDBC eller logning på applikationsniveau og databaseovervågning.
Baggrund
Dette afsnit beskriver noget af teorien bag markører, og hvordan JDBC skal bruges. Hvis du ikke har brug for at kende baggrunden, kan du springe dette over og gå direkte til 'Eliminering af lækager'.
Hvad er en markør?
En markør er en ressource på databasen, der har status for en forespørgsel, specifikt den position, hvor en læser er i et resultatsæt. Hver SELECT-sætning har en markør, og PL/SQL-lagrede procedurer kan åbne og bruge så mange markører, som de kræver. Du kan finde ud af mere om markører på Orafaq.
En databaseinstans tjener typisk flere forskellige skemaer , mange forskellige brugere hver med flere sessioner . For at gøre dette har den et fast antal markører til rådighed for alle skemaer, brugere og sessioner. Når alle markører er åbne (i brug), og der kommer en anmodning ind, der kræver en ny markør, mislykkes anmodningen med en ORA-010000 fejl.
Find og indstilling af antallet af markører
Nummeret konfigureres normalt af DBA ved installation. Antallet af markører, der aktuelt er i brug, det maksimale antal og konfigurationen kan tilgås i administratorfunktionerne i Oracle SQL Developer. Fra SQL kan den indstilles med:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Relaterer JDBC i JVM til markører på DB
JDBC-objekterne nedenfor er tæt koblet til følgende databasekoncepter:
- JDBC forbindelse er klientrepræsentationen af en database-session og giver database transaktioner . En forbindelse kan kun have en enkelt transaktion åben ad gangen (men transaktioner kan indlejres)
- Et JDBC Resultatsæt understøttes af en enkelt markør på databasen. Når close() kaldes på ResultSet, slippes markøren.
- En JDBC CallableStatement påberåber en lagret procedure på databasen, ofte skrevet i PL/SQL. Den lagrede procedure kan oprette nul eller flere markører og kan returnere en markør som et JDBC-resultatsæt.
JDBC er trådsikker:Det er helt OK at sende de forskellige JDBC-objekter mellem tråde.
For eksempel kan du oprette forbindelsen i én tråd; en anden tråd kan bruge denne forbindelse til at oprette en PreparedStatement og en tredje tråd kan behandle resultatsættet. Den store begrænsning er, at du ikke kan have mere end ét ResultSet åbent på et enkelt PreparedStatement til enhver tid. Se Understøtter Oracle DB flere (parallelle) operationer pr. forbindelse?
Bemærk, at en database-commit forekommer på en forbindelse, så alle DML (INSERT, UPDATE og DELETE's) på den forbindelse vil commite sammen. Derfor, hvis du ønsker at understøtte flere transaktioner på samme tid, skal du have mindst én forbindelse for hver samtidige transaktion.
Lukning af JDBC-objekter
Et typisk eksempel på udførelse af et resultatsæt er:
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) { }
}
Bemærk, hvordan finally-sætningen ignorerer enhver undtagelse, der er rejst af close():
- Hvis du blot lukker resultatsættet uden at prøve {} catch {}, kan det mislykkes og forhindre, at erklæringen lukkes
- Vi vil gerne tillade, at enhver undtagelse, der er rejst i hovedteksten af forsøget, spredes til den, der ringer. Hvis du har en loop over for eksempel at oprette og udføre udsagn, skal du huske at lukke hvert udsagn i loopet.
I Java 7 har Oracle introduceret den AutoCloseable-grænseflade, som erstatter det meste af Java 6-kedelpladen med noget godt syntaktisk sukker.
Holder JDBC-objekter
JDBC-objekter kan opbevares sikkert i lokale variabler, objektforekomster og klassemedlemmer. Det er generelt bedre praksis at:
- Brug objektforekomst eller klassemedlemmer til at opbevare JDBC-objekter, der genbruges flere gange over en længere periode, såsom Connections og PreparedStatements
- Brug lokale variabler til resultatsæt, da disse opnås, føres over og lukkes typisk inden for rammerne af en enkelt funktion.
Der er dog en undtagelse:Hvis du bruger EJB'er eller en Servlet/JSP-beholder, skal du følge en streng gevindmodel:
- Kun applikationsserveren opretter tråde (som den håndterer indgående anmodninger med)
- Kun applikationsserveren opretter forbindelser (som du får fra forbindelsespuljen)
- Når du gemmer værdier (tilstand) mellem opkald, skal du være meget forsigtig. Gem aldrig værdier i dine egne caches eller statiske medlemmer - dette er ikke sikkert på tværs af klynger og andre mærkelige forhold, og applikationsserveren kan gøre forfærdelige ting ved dine data. Brug i stedet stateful bønner eller en database.
- I særdeleshed aldrig hold JDBC-objekter (Connections, ResultSets, PreparedStatements osv.) over forskellige fjernankaldelser - lad applikationsserveren styre dette. Applikationsserveren giver ikke kun en forbindelsespulje, den cacher også dine PreparedStatements.
Eliminering af lækager
Der er en række processer og værktøjer tilgængelige til at hjælpe med at opdage og eliminere JDBC-lækager:
-
Under udvikling - at fange fejl tidligt er langt den bedste tilgang:
-
Udviklingspraksis:God udviklingspraksis bør reducere antallet af fejl i din software, før den forlader udviklerens skrivebord. Specifikke praksisser omfatter:
- Parprogrammering for at uddanne dem uden tilstrækkelig erfaring
- Kode anmeldelser, fordi mange øjne er bedre end én
- Enhedstest, hvilket betyder, at du kan udøve enhver og al din kodebase fra et testværktøj, som gør gengivelse af lækager trivielt.
- Brug eksisterende biblioteker til pooling af forbindelser i stedet for at bygge dine egne
-
Statisk kodeanalyse:Brug et værktøj som den fremragende Findbugs til at udføre en statisk kodeanalyse. Dette opfanger mange steder, hvor close() ikke er blevet håndteret korrekt. Findbugs har et plugin til Eclipse, men det kører også selvstændigt til engangsfunktioner, har integrationer i Jenkins CI og andre byggeværktøjer
-
-
Ved kørsel:
-
Holdbarhed og forpligtelse
- Hvis ResultSet holdability er ResultSet.CLOSE_CURSORS_OVER_COMMIT, lukkes ResultSet, når Connection.commit() metoden kaldes. Dette kan indstilles ved hjælp af Connection.setHoldability() eller ved at bruge den overbelastede Connection.createStatement()-metode.
-
Logning under kørsel.
- Sæt gode log-udsagn i din kode. Disse skal være klare og forståelige, så kunden, supportpersonalet og holdkammeraterne kan forstå det uden træning. De skal være kortfattede og omfatte udskrivning af tilstanden/interne værdier for nøglevariabler og attributter, så du kan spore behandlingslogik. God logning er grundlæggende for fejlfinding af applikationer, især dem, der er blevet implementeret.
-
Du kan tilføje en debugging JDBC driver til dit projekt (til debugging - faktisk ikke implementere det). Et eksempel (jeg har ikke brugt det) er log4jdbc. Du skal derefter lave en simpel analyse af denne fil for at se, hvilke eksekveringer der ikke har en tilsvarende afslutning. At tælle åbne og lukke bør fremhæve, om der er et potentielt problem
- Overvågning af databasen. Overvåg dit kørende program ved hjælp af værktøjer såsom SQL Developer 'Monitor SQL'-funktionen eller Quest's TOAD. Overvågning er beskrevet i denne artikel. Under overvågningen forespørger du de åbne markører (f.eks. fra tabellen v$sesstat) og gennemgår deres SQL. Hvis antallet af markører stiger, og (vigtigst) bliver domineret af én identisk SQL-sætning, ved du, at du har en lækage med den SQL. Søg i din kode og anmeld.
-
Andre tanker
Kan du bruge WeakReferences til at håndtere lukkede forbindelser?
Svage og bløde referencer er måder at give dig mulighed for at referere til et objekt på en måde, der gør det muligt for JVM at indsamle referenten til enhver tid, den finder passende (forudsat at der ikke er stærke referencekæder til det objekt).
Hvis du sender en ReferenceQueue i konstruktøren til den bløde eller svage Reference, placeres objektet i ReferenceQueue, når objektet er GC'et, når det opstår (hvis det overhovedet forekommer). Med denne tilgang kan du interagere med objektets færdiggørelse, og du kan lukke eller afslutte objektet på det tidspunkt.
Fantomreferencer er lidt mærkeligere; deres formål er kun at kontrollere færdiggørelsen, men du kan aldrig få en reference til det originale objekt, så det bliver svært at kalde close()-metoden på det.
Det er dog sjældent en god idé at forsøge at kontrollere, hvornår GC'en køres (Weak, Soft og PhantomReferences giver dig besked efter kendsgerningen at objektet er i kø til GC). Faktisk, hvis mængden af hukommelse i JVM er stor (f.eks. -Xmx2000m), kan du aldrig GC objektet, og du vil stadig opleve ORA-01000. Hvis JVM-hukommelsen er lille i forhold til dit programs krav, kan du opleve, at ResultSet- og PreparedStatement-objekterne er GCed umiddelbart efter oprettelsen (før du kan læse fra dem), hvilket sandsynligvis vil fejle dit program.
TL;DR: Den svage referencemekanisme er ikke en god måde at administrere og lukke Statement- og ResultSet-objekter på.