2011-08-04 1 views
4

MySQL과 Java를 사용하여 약 50000 개의 레코드를 선택하고 있습니다. 이상한 점은 ResultSet 및 next() 메서드를 사용하여 데이터를 읽을 때 페치 중에 내 Java 응용 프로그램의 RAM 사용량이 증가한다는 것입니다. 255MB로 시작하여 최대 379MB까지 증가합니다! 내가 사용하고 코드가 여기에 있습니다 :mysql 메모리 (RAM) 사용량이 ResultSet을 사용하는 동안 증가합니까?

try { 
    Class.forName("com.mysql.jdbc.Driver"); 
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/#mysql50#crawler - used in report?" + "user=root&password=&useUnicode=true&characterEncoding=UTF-8"); 
    Statement st = conn.createStatement(); 
    ResultSet rsDBReader = st.executeQuery("SELECT Id, Content FROM DocsArchive"); 
    while (rsDBReader.next()) { 
     int docId = rsDBReader.getInt(1); 
     String content = rsDBReader.getString(2); 
     . . . 
     } 
    rsDBReader.close(); 
    st.close(); 
    conn.close(); 
} catch (Exception e) { 
    System.out.println("Exception in reading data: " + e); 
} 

나는 메모리 사용량이의 ResultSet위한 것임을 확신하지 프로그램의 다른 부분. 이 프로그램에서는 레코드를 업데이트 할 필요가 없으므로 작업을 마친 후 모든 레코드를 제거하고 싶습니다. 제 생각에 읽은 레코드는 제거되지 않고 프로그램은 메모리를 비우지 않습니다. 그래서 다음과 같은 코드를 사용하는 것과 같이이를 피하기 위해 몇 가지 트릭을 사용했습니다.

Statement st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); 

st.setFetchSize(500); 
rsDBReader.setFetchSize(500); 

하지만 아무 것도 변경하지 않았습니다. :(

그래서 내가 읽은 행 (릴리스) 메모리를 제거하는 몇 가지 방법이 필요합니다.

또 다른 흥미로운 점은 심지어 기능을 마무리 된 ResultSet, 성명 및 연결을 폐쇄하고가는 후 프로그램의 다른 부분은 여전히 ​​프로그램의 메모리 사용량은 줄어들지 않습니다! 감사

+2

MySQL은 쿼리 결과를 캐시합니다. – Johan

+0

고마워,하지만 어떻게 캐시를 비울 수 있니? – Soheil

+0

메모리를 절약하기 위해 할 수있는 일은 SELECT 문당 얻을 수있는 결과의 수를 제한하는 것입니다. – RMT

답변

2

나는 당신이 당신의 쿼리에서 검색하는 행의 양을 제한 건의 할 것입니다. 50000이 많은, 그래서 왜 루프를 가지고 있지 매번 1000 행을 가져옵니다.

here에 설명 된대로 limit 문을 사용하여이를 수행 할 수 있습니다. 처리중인 데이터의 양에 대해서는 항상 실용적 인 것이 가장 좋습니다. 현재 선택한 셀은 오늘 50000 개의 행을 반환 할 수 있지만 내일 100 만 개가되면 어떻게됩니까? 응용 프로그램이 중단됩니다. 따라서 단계별로 처리하십시오.

6

Statement.setFetchSize()을 사용하면 특정 행 수를 포함하는 행을 ResultSet으로 스트리밍해야한다는 힌트를 드라이버에 제공 할 수 있습니다. 내가 아는 한, MySQL Connector-J 드라이버는 힌트와 스트림을 이해하지 못한다. () (MySQL의 경우 한 번에 한 행씩 제한된다.)

기본값 인 0은 Connector-J 드라이버가 스트리밍하지 않고 전체 ResultSet을 가져 오도록합니다. 따라서 MySQL의 경우 Integer.MIN_VALUE라는 명시적인 값을 제공해야합니다.

문 :

Statement st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); 

ResultSet (적어도 그것의 자신의 협정에) 스트리밍 발생하지 않습니다. 트랜잭션이 커밋 될 때 결과 집합이 "스크롤 가능"(즉, 순방향에서만 트래버스 될 수 있음) 및 "업데이트 가능"하지 않고 기본 커서가 닫히게합니다.

JDBC implementation notes of MySQL에서 언급했듯이 위의 문 (ResultSet.CLOSE_CURSORS_AT_COMMIT 매개 변수없이)은 Statement.setFetchSize(Integer.MIN_VALUE) 호출과 함께 호출되어 스트리밍이 행별로 발생해야합니다. 이와 같은 시나리오와 관련된 경고는 문서화되어 있습니다.

커서의 보관 기능은 MySQL 설명서에 언급 된 예제에서 지정되지 않았다는 점에 유의하십시오.Connection.getHoldability()에서 제공 한 것과 다른 값이 필요하면이 권고가 적용되지 않을 수 있습니다.

+0

아니요, MySQL JDBC 드라이버는 가져 오기 크기 제어에 대한 지원이 매우 제한되어 있습니다. 기본 동작은 select의 전체 결과를 한 번에 가져 오는 것입니다. 결과를 스트리밍하려면 반입 크기를 Integer.MIN_VALUE로 설정하고 Statement를 ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY로 작성해야합니다. 이 작업을 수행 할 때 설명서에 명시된 바와 같이 몇 가지 추가 제한 사항을 고려해야합니다. http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html – jarnbjo

+0

코드 : st.setFetchSize (500); rsDBReader.setFetchSize (500); 하지만 아무 것도 바뀌지 않았습니다 – Soheil

+0

@jarnbjo, 고마워. 내가 부분적으로 만 정확하다는 것이 밝혀졌습니다. –

-1

실제로 예상되는 동작이며 반드시 메모리 누수를 나타내는 것은 아닙니다. 객체 인스턴스는 도달 할 수 없게 된 후 즉시 가비지 수집되지 않으며 대부분의 Java VM은 할당 된 메모리를 다시 운영 체제로 반환하는 것을 매우 꺼립니다.

오라클의 Java VM의 최신 버전을 사용하는 경우 정말 당신이 자바 명령에 다음 인수를 추가하여 G1GC 구현을 시도 할 수 있습니다, 더 적극적인 가비지 컬렉터가 필요합니다

-XX : + UnlockExperimentalVMOptions를 - XX : + UseG1GC

G1GC 가비지 수집기는 일반적으로 기본 가비지 수집기보다 빠르게 개체를 회수하고 사용되지 않은 메모리도 프로세스에서 해제됩니다.

0

Postgres의 최신 릴리스에서 비슷한 문제가 있음에 유의하십시오. 커서 처리를 수행하려면 *connection.setAutoCommit(false)에 자동 커밋을 사용하지 말고 SQL 문에서 단일 명령문을 사용하십시오 (예 : 세미콜론이 하나만있는 명령문). 그것은 나를 위해 일했습니다.

Postgres JDBC documentation