[java] java.sql.SQLException :-ORA-01000 : 최대 열린 커서를 초과했습니다.

ORA-01000 SQL 예외가 발생합니다. 그래서 관련 질문이 있습니다.

  1. 최대 열린 커서가 JDBC 연결 수와 정확히 관련이 있습니까, 아니면 단일 연결에 대해 만든 명령문 및 결과 집합 개체와도 관련이 있습니까? (우리는 연결 풀을 사용하고 있습니다)
  2. 연결과 같은 데이터베이스의 문 / 결과 집합 개체 수를 구성하는 방법이 있습니까?
  3. 단일 스레드 환경에서 메서드 local 문 / resultset 개체 대신 인스턴스 변수 문 / resultset 개체를 사용하는 것이 좋습니까?
  4. 루프에서 준비된 명령문을 실행하면이 문제가 발생합니까? (물론 sqlBatch를 사용할 수도있었습니다.) 참고 : pStmt는 루프가 끝나면 닫힙니다.

    { //method try starts  
      String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
      pStmt = obj.getConnection().prepareStatement(sql);
      pStmt.setLong(1, subscriberID);
      for (String language : additionalLangs) {
        pStmt.setInt(2, Integer.parseInt(language));
        pStmt.execute();
      }
    } //method/try ends
    
    { //finally starts
       pStmt.close()
    } //finally ends 
    
  5. conn.createStatement () 및 conn.prepareStatement (sql)가 단일 연결 객체에서 여러 번 호출되면 어떻게됩니까?

Edit1 :
6. Weak / Soft 참조 문 개체를 사용하면 누출 방지에 도움이됩니까?

Edit2 :
1. 프로젝트에서 누락 된 “statement.close ()”를 모두 찾을 수있는 방법이 있습니까? 나는 그것이 메모리 누출이 아니라는 것을 이해합니다. 하지만 가비지 수집에 적합한 문 참조 (close ()가 수행되지 않는 곳)를 찾아야합니까? 사용 가능한 도구가 있습니까? 아니면 수동으로 분석해야합니까?

이해하도록 도와주세요.

해결책

Oracle DB에서 사용자 이름 -VELU에 대해 열린 커서를 찾으려면

ORACLE 시스템으로 이동하여 sqlplus를 sysdba로 시작하십시오.

[oracle@db01 ~]$ sqlplus / as sysdba 

그런 다음 실행

SELECT   A.VALUE,
    S.USERNAME,
    S.SID,
    S.SERIAL#
  FROM V$SESSTAT A,
    V$STATNAME B,
    V$SESSION S
  WHERE A.STATISTIC# = B.STATISTIC#
    AND S.SID        = A.SID
    AND B.NAME       = 'opened cursors current'
    AND USERNAME     = 'VELU';

가능하면 내 솔루션에 대한 더 많은 이해를 위해 내 대답을 읽으십시오.



답변

최대 개방 커서 오류 인 ORA-01000은 Oracle 데이터베이스 개발에서 매우 일반적인 오류입니다. Java 컨텍스트에서 애플리케이션이 데이터베이스 인스턴스에 구성된 커서보다 더 많은 ResultSet을 열려고 할 때 발생합니다.

일반적인 원인은 다음과 같습니다.

  1. 구성 실수

    • 데이터베이스의 커서보다 데이터베이스를 쿼리하는 응용 프로그램의 스레드가 더 많습니다. 한 가지 경우는 데이터베이스의 커서 수보다 큰 연결 및 스레드 풀이있는 경우입니다.
    • 동일한 DB 인스턴스 (아마도 많은 스키마를 포함 할 것임)에 많은 개발자 또는 애플리케이션이 연결되어 있고 함께 너무 많은 연결을 사용하고 있습니다.
    • 해결책:

      • 데이터베이스 에서 커서 수 늘리기 (자원이 허용하는 경우) 또는
      • 응용 프로그램의 스레드 수를 줄입니다.
  2. 커서 누수

    • 애플리케이션이 ResultSet (JDBC) 또는 커서 (데이터베이스의 스토어드 프로 시저에서)를 닫지 않습니다.
    • 솔루션 : 커서 누수는 버그입니다. DB에서 커서 수를 늘리면 불가피한 실패가 지연됩니다. 누수는 정적 코드 분석 , JDBC 또는 애플리케이션 수준 로깅, 데이터베이스 모니터링을 사용하여 찾을 수 있습니다 .

배경

이 섹션에서는 커서 뒤에있는 몇 가지 이론과 JDBC를 사용하는 방법에 대해 설명합니다. 배경을 알 필요가 없으면이 단계를 건너 뛰고 바로 ‘Eliminating Leaks’로 이동할 수 있습니다.

커서 란?

커서는 쿼리의 상태, 특히 판독기가 ResultSet에있는 위치를 보유하는 데이터베이스의 리소스입니다. 각 SELECT 문에는 커서가 있으며 PL / SQL 저장 프로시 저는 필요한만큼 많은 커서를 열고 사용할 수 있습니다. Orafaq에서 커서에 대해 자세히 알아볼 수 있습니다 .

데이터베이스 인스턴스는 일반적으로 여러 가지 역할을 스키마 , 많은 다른 사용자 와 각각의 여러 세션을 . 이를 위해 모든 스키마, 사용자 및 세션에 사용할 수있는 고정 된 수의 커서가 있습니다. 모든 커서가 열려 (사용 중) 새 커서가 필요한 요청이 들어 오면 ORA-010000 오류와 함께 요청이 실패합니다.

커서 수 찾기 및 설정

번호는 일반적으로 설치시 DBA가 구성합니다. 현재 사용중인 커서 수, 최대 수 및 구성은 Oracle SQL Developer 의 관리자 기능에서 액세스 할 수 있습니다 . SQL에서 다음과 같이 설정할 수 있습니다.

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

JVM의 JDBC를 DB의 커서에 연결

아래의 JDBC 개체는 다음 데이터베이스 개념과 밀접하게 연결되어 있습니다.

  • JDBC 연결 은 데이터베이스 세션 의 클라이언트 표현이며 데이터베이스 트랜잭션을 제공합니다 . 연결은 한 번에 하나의 트랜잭션 만 열 수 있지만 트랜잭션은 중첩 될 수 있습니다.
  • JDBC ResultSet 은 데이터베이스 의 단일 커서 에서 지원됩니다 . ResultSet에서 close ()가 호출되면 커서가 해제됩니다.
  • JDBC CallableStatement 는 종종 PL / SQL로 작성된 데이터베이스에서 저장 프로 시저 를 호출합니다 . 스토어드 프로시 저는 0 개 이상의 커서를 작성할 수 있으며 커서를 JDBC ResultSet으로 리턴 할 수 있습니다.

JDBC는 스레드로부터 안전합니다. 스레드간에 다양한 JDBC 개체를 전달하는 것은 괜찮습니다.

예를 들어, 하나의 스레드에서 연결을 작성할 수 있습니다. 다른 스레드는이 연결을 사용하여 PreparedStatement를 만들 수 있고 세 번째 스레드는 결과 집합을 처리 할 수 ​​있습니다. 하나의 주요 제한 사항은 하나의 PreparedStatement에서 한 번에 둘 이상의 ResultSet을 열 수 없다는 것입니다. Oracle DB는 연결 당 여러 (병렬) 작업을 지원합니까?를 참조하십시오 .

데이터베이스 커밋은 Connection에서 발생하므로 해당 연결의 모든 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) { }
}

finally 절이 close ()에 의해 발생한 예외를 무시하는 방법에 유의하십시오.

  • try {} catch {}없이 단순히 ResultSet을 닫으면 실패하고 Statement가 닫히지 않을 수 있습니다.
  • 호출자에게 전파하려는 시도의 본문에서 발생한 모든 예외를 허용하려고합니다. 예를 들어 문을 만들고 실행하는 것과 같이 루프가있는 경우 루프 내에서 각 문을 닫아야합니다.

Java 7에서 Oracle은 대부분의 Java 6 상용구를 멋진 구문 설탕으로 대체 하는 AutoCloseable 인터페이스 를 도입했습니다 .

JDBC 객체 보유

JDBC 객체는 로컬 변수, 객체 인스턴스 및 클래스 멤버에 안전하게 보관할 수 있습니다. 일반적으로 다음을 수행하는 것이 더 좋습니다.

  • 객체 인스턴스 또는 클래스 멤버를 사용하여 Connections 및 PreparedStatements와 같이 장기간에 걸쳐 여러 번 재사용되는 JDBC 객체를 보유합니다.
  • ResultSets에 로컬 변수를 사용하십시오. 이는 일반적으로 단일 함수의 범위 내에서 확보되고 반복 된 다음 닫힙니다.

그러나 한 가지 예외가 있습니다. EJB 또는 Servlet / JSP 컨테이너를 사용하는 경우 엄격한 스레딩 모델을 따라야합니다.

  • Application Server 만 스레드를 작성합니다 (수신 요청을 처리하는 데 사용)
  • 응용 프로그램 서버 만 연결을 만듭니다 (연결 풀에서 가져옴).
  • 통화 사이에 값 (상태)을 저장할 때 매우주의해야합니다. 자신의 캐시 나 정적 멤버에 값을 저장하지 마십시오. 이것은 클러스터 및 기타 이상한 조건에서 안전하지 않으며 Application Server가 데이터에 끔찍한 일을 할 수 있습니다. 대신 Stateful Bean 또는 데이터베이스를 사용하십시오.
  • 특히, 서로 다른 원격 호출에 대해 JDBC 객체 (연결, ResultSets, PreparedStatements 등)를 보유 하지 마십시오 . Application Server가이를 관리하도록하십시오. Application Server는 연결 풀을 제공 할뿐만 아니라 PreparedStatements도 캐시합니다.

누출 제거

JDBC 누출을 감지하고 제거하는 데 도움이되는 여러 프로세스와 도구가 있습니다.

  1. 개발 중-버그를 조기에 발견하는 것이 가장 좋은 방법입니다.

    1. 개발 관행 : 좋은 개발 관행은 소프트웨어가 개발자의 책상을 떠나기 전에 소프트웨어의 버그 수를 줄여야합니다. 구체적인 사례는 다음과 같습니다.

      1. 충분한 경험이없는 사람들을 교육하기위한 페어 프로그래밍
      2. 많은 눈이 하나보다 낫기 때문에 코드 검토
      3. 단위 테스트 는 누수 재현을 사소하게 만드는 테스트 도구에서 모든 코드베이스를 실행할 수 있음을 의미합니다.
      4. 자신 만의 라이브러리 를 구축하는 대신 연결 풀링에 기존 라이브러리 사용
    2. 정적 코드 분석 : 뛰어난 Findbugs 와 같은 도구 를 사용하여 정적 코드 분석을 수행합니다. 이것은 close ()가 올바르게 처리되지 않은 많은 곳을 선택합니다. Findbugs에는 Eclipse 용 플러그인이 있지만 일회성으로 독립형으로 실행되며 Jenkins CI 및 기타 빌드 도구에 통합됩니다.

  2. 런타임시 :

    1. 유지성 및 커밋

      1. ResultSet 보유 가능성이 ResultSet.CLOSE_CURSORS_OVER_COMMIT이면 Connection.commit () 메소드가 호출 될 때 ResultSet이 닫힙니다. Connection.setHoldability ()를 사용하거나 오버로드 된 Connection.createStatement () 메서드를 사용하여 설정할 수 있습니다.
    2. 런타임에 로깅.

      1. 코드에 좋은 로그 문을 넣으십시오. 고객, 지원 직원 및 팀원이 교육없이 이해할 수 있도록 명확하고 이해할 수 있어야합니다. 그것들은 간결해야하며 처리 로직을 추적 할 수 있도록 키 변수 및 속성의 상태 / 내부 값 인쇄를 포함해야합니다. 좋은 로깅은 특히 배포 된 애플리케이션을 디버깅하기위한 기본입니다.
      2. 프로젝트에 디버깅 JDBC 드라이버를 추가 할 수 있습니다 (디버깅을 위해-실제로 배포하지 마십시오). 한 가지 예 (사용하지 않았 음)는 log4jdbc 입니다. 그런 다음이 파일에 대해 간단한 분석을 수행하여 해당 종료가없는 실행을 확인해야합니다. 잠재적 인 문제가있는 경우 시가 및 시가를 세어 강조해야합니다.

        1. 데이터베이스 모니터링. SQL Developer ‘Monitor SQL’기능 또는 Quest의 TOAD 와 같은 도구를 사용하여 실행중인 애플리케이션을 모니터링합니다 . 이 문서에서는 모니터링에 대해 설명 합니다. 모니터링 중에 열린 커서 (예 : v $ sesstat 테이블에서)를 쿼리하고 해당 SQL을 검토합니다. 커서 수가 증가하고 (가장 중요한 것은) 하나의 동일한 SQL 문에 의해 지배되는 경우 해당 SQL에 누수가 있음을 알 수 있습니다. 코드를 검색하고 검토하십시오.

다른 생각들

WeakReferences를 사용하여 연결 닫기를 처리 할 수 ​​있습니까?

약한 참조와 소프트 참조는 JVM이 적합하다고 판단 할 때 언제든지 참조 대상을 가비지 수집 할 수있는 방식으로 개체를 참조 할 수 있도록하는 방법입니다 (해당 개체에 대한 강력한 참조 체인이 없다고 가정).

생성자의 ReferenceQueue를 소프트 또는 약한 참조로 전달하면 객체가 발생했을 때 GC 될 때 객체가 ReferenceQueue에 배치됩니다 (아마도 발생하는 경우). 이 접근 방식을 사용하면 개체의 마무리와 상호 작용할 수 있으며 그 순간 개체를 닫거나 마무리 할 수 ​​있습니다.

팬텀 참조는 좀 더 이상합니다. 그들의 목적은 종료를 제어하는 ​​것 뿐이지 만 원래 객체에 대한 참조를 얻을 수 없으므로 close () 메서드를 호출하기가 어려울 것입니다.

그러나 GC가 실행되는시기를 제어하는 ​​것은 좋은 생각이 아닙니다 (Weak, Soft 및 PhantomReferences 는 객체가 GC를 위해 대기열에 추가 되었다는 사실 을 알려줍니다 ). 실제로 JVM의 메모리 양이 큰 경우 (예 : -Xmx2000m) 객체를 GC 하지 않을 수 있으며 ORA-01000이 계속 발생합니다. JVM 메모리가 프로그램의 요구 사항에 비해 작 으면 ResultSet 및 PreparedStatement 객체가 생성 직후 (읽기 전에) GC 처리되어 프로그램이 실패 할 수 있습니다.

요약 : 약한 참조 메커니즘은 Statement 및 ResultSet 개체를 관리하고 닫는 좋은 방법이 아닙니다.


답변

나는 이해를 좀 더 추가하고있다.

  1. Cursor는 objecct 문에 관한 것입니다. resultSet도 연결 개체도 아닙니다.
  2. 그러나 여전히 오라클 메모리를 확보하기 위해 결과 집합을 닫아야합니다. CURSORS에 포함되지 않는 결과 집합을 닫지 않은 경우에도 마찬가지입니다.
  3. Closing Statement 개체는 결과 집합 개체도 자동으로 닫습니다.
  4. 모든 SELECT / INSERT / UPDATE / DELETE 문에 대해 커서가 생성됩니다.
  5. 각 ORACLE DB 인스턴스는 oracle SID를 사용하여 식별 할 수 있습니다. 마찬가지로 ORACLE DB는 연결 SID를 사용하여 각 연결을 식별 할 수 있습니다. 두 SID가 모두 다릅니다.
  6. 따라서 ORACLE 세션은 jdbc (tcp) 연결 일뿐입니다. 하나의 SID 일뿐입니다.
  7. 최대 커서를 500으로 설정하면 하나의 JDBC 세션 / 연결 / SID에만 해당됩니다.
  8. 따라서 우리는 각각의 커서 (문)없이 많은 JDBC 연결을 가질 수 있습니다.
  9. JVM이 종료되면 모든 연결 / 커서가 닫히거나 JDBCConnection이 닫히면 해당 연결에 대한 CURSORS가 닫힙니다.

sysdba로 로그인합니다.

Putty에서 (Oracle 로그인) :

  [oracle@db01 ~]$ sqlplus / as sysdba

SqlPlus에서 :

사용자 이름: sys as sysdba

닫힌 커서가 없도록 session_cached_cursors 값을 0으로 설정하십시오.

 alter session set session_cached_cursors=0
 select * from V$PARAMETER where name='session_cached_cursors'

DB에서 연결 당 기존 OPEN_CURSORS 값 집합 선택

 SELECT max(a.value) as highest_open_cur, p.value as max_open_cur FROM v$sesstat a, v$statname b, v$parameter p WHERE a.statistic# = b.statistic# AND b.name = 'opened cursors current' AND p.name= 'open_cursors'  GROUP BY p.value;

다음은 열린 커서 값이있는 SID / 연결 목록을 찾는 쿼리입니다.

 SELECT a.value, s.username, s.sid, s.serial#
 FROM v$sesstat a, v$statname b, v$session s
 WHERE a.statistic# = b.statistic#  AND s.sid=a.sid 
 AND b.name = 'opened cursors current' AND username = 'SCHEMA_NAME_IN_CAPS'

아래 쿼리를 사용하여 열린 커서에서 SQL을 식별하십시오.

 SELECT oc.sql_text, s.sid 
 FROM v$open_cursor oc, v$session s
 WHERE OC.sid = S.sid
 AND s.sid=1604
 AND OC.USER_NAME ='SCHEMA_NAME_IN_CAPS'

이제 코드를 디버그하고 즐기십시오 !!! 🙂


답변

다음과 같이 코드를 수정하십시오.

try
{ //method try starts  
  String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
  pStmt = obj.getConnection().prepareStatement(sql);
  pStmt.setLong(1, subscriberID);
  for (String language : additionalLangs) {
    pStmt.setInt(2, Integer.parseInt(language));
    pStmt.execute();
  }
} //method/try ends
finally
{ //finally starts
   pStmt.close()
} 

정말로 당신의 pStatements, 연결 및 결과를 닫고 있다고 확신합니까?

열린 객체를 분석하기 위해 statemant, 연결 및 결과 객체를 코드로 감싸는 위임자 패턴을 구현할 수 있습니다. 따라서 개체가 성공적으로 닫혔는지 확인할 수 있습니다.

예 : pStmt = obj. getConnection (). prepareStatement (sql);

    class obj{ 

    public Connection getConnection(){
    return new ConnectionDelegator(...here create your connection object and put it into ...);

    } 
}


class ConnectionDelegator implements Connection{
    Connection delegates;

    public ConnectionDelegator(Connection con){
       this.delegates = con;
    }

    public Statement prepareStatement(String sql){
        return delegates.prepareStatement(sql);
    }

    public void close(){
        try{
           delegates.close();
        }finally{
           log.debug(delegates.toString() + " was closed");
        }
    }
}


답변

애플리케이션이 Oracle WebLogic에서 애플리케이션 서버로 실행중인 Java EE 애플리케이션 인 경우이 문제의 가능한 원인 은 WebLogic 의 Statement Cache Size 설정입니다.

특정 데이터 소스에 대한 Statement Cache Size 설정이 Oracle 데이터베이스의 최대 열린 커서 수 설정과 거의 같거나 크면 WebLogic에서 열린 상태로 유지되는 캐시 된 SQL 문에서 열린 커서를 모두 사용할 수 있습니다. ORA-01000 오류.

이 문제를 해결하려면 Oracle 데이터베이스를 가리키는 각 WebLogic 데이터 원본에 대한 Statement Cache Size 설정을 데이터베이스의 최대 커서 수 설정보다 크게 줄이십시오.

WebLogic 10 관리 콘솔에서 각 데이터 원본에 대한 문 캐시 크기 설정은 서비스 (왼쪽 탐색)> 데이터 원본> (개별 데이터 원본)> 연결 풀 탭에서 찾을 수 있습니다.


답변

저도이 문제에 직면했습니다.

java.sql.SQLException: - ORA-01000: maximum open cursors exceeded

나는 dao 레이어에 Spring JDBC 와 함께 Spring Framework 를 사용하고 있었다 .

내 응용 프로그램은 어떻게 든 커서를 누출하는 데 사용되었으며 몇 분 정도 후에이 예외를 제공했습니다.

철저한 디버깅과 분석을 거친 후, 실행중인 쿼리 에서 사용중인 테이블 중 하나에 인덱싱, 기본 키 및 고유 제약 조건 에 문제가 있음을 발견했습니다 .

내 응용 프로그램 이 실수로 인덱싱 된 을 업데이트하려고했습니다 . 따라서 내 응용 프로그램이 인덱싱 된 열에 대한 업데이트 쿼리를 칠 때마다 데이터베이스는 업데이트 된 값을 기반으로 재 인덱싱을 수행하려고했습니다. 커서 가 누출되었습니다 .

쿼리에서 검색하는 데 사용 된 열에 대해 적절한 인덱싱을 수행하고 필요할 때마다 적절한 제약 조건을 적용하여 문제를 해결할 수있었습니다.


답변

오늘도 같은 문제 (ORA-01000)에 직면했습니다. try {}에 for 루프가있어서 Oracle DB에서 SELECT 문을 여러 번 실행하고 (매번 매개 변수를 변경할 때마다) finally {}에서는 Resultset, PreparedStatement 및 Connection을 평소와 같이 닫는 코드를 가졌습니다. . 그러나 특정 양의 루프 (1000)에 도달하자마자 너무 많은 열린 커서에 대한 Oracle 오류가 발생했습니다.

위의 Andrew Alcock의 게시물을 기반으로 루프 내부 에서 데이터를 가져온 후 다시 루프하기 전에 각 결과 집합과 각 문을 닫고 문제가 해결되도록 변경했습니다.

또한, 다른 Oracle DB (ORA-01000)의 다른 Insert 문 루프에서 똑같은 문제가 발생했습니다. 다시 동일한 방식으로 해결되었으므로 PreparedStatement 또는 ResultSet 또는 둘 다 닫힐 때까지 열린 커서로 계산됩니다.


답변

autocommit = true로 설정 했습니까? 이것을 시도하지 않으면 :

{ //method try starts  
    String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
    Connection conn = obj.getConnection()
    pStmt = conn.prepareStatement(sql);

    for (String language : additionalLangs) {
        pStmt.setLong(1, subscriberID);
        pStmt.setInt(2, Integer.parseInt(language));
        pStmt.execute();
        conn.commit();
    }
} //method/try ends { 
    //finally starts
    pStmt.close()
} //finally ends