2012-05-10 2 views
0

DB에 연결하여 중복 항목을 확인하고 복제본을 삭제하고 원본 레코드의 동일한 텍스트 수를 업데이트하는 프로그램이 있습니다.JDBC 쿼리 최적화

제 문제는 단지 100000 개의 레코드로 몇 시간이 걸리고 프로그램이 '힙 스페이스 필요'오류로 종료된다는 것입니다.

어떻게하면이 프로그램을 최적화하여 작업을 빠르게 처리 할 수 ​​있습니까?

public class Main { 
    // serveropsdb.properties file location = /opt/serverops/scripts/serverops.properties 
    private final static String PROPERTIES_FILENAME=File.separatorChar+"opt"+File.separatorChar+"serverops"+File.separatorChar+"scripts"+File.separatorChar+"serveropsdb.properties"; 
    private final static String DATE_FORMAT = "yyyy/MM/dd HH:mm:ss"; 
    private final static String PROPERTY_NAME_STARTTIME= "start.time"; 
    private final static String PROPERTY_NAME_ENDTIME= "end.time"; 
    private final static String PROPERTY_NAME_DB_TABLENAME= "db.tablename"; 
    private final static String PROPERTY_NAME_DB_USERNAME= "db.username"; 
    private final static String PROPERTY_NAME_DB_PASSWORD= "db.password"; 
    private final static String PROPERTY_NAME_DB_NAME= "db.name"; 

    public static void main(String[] args) { 
    System.out.println("DB consolidation started"); 

    Properties properties = new Properties(); 
    try { //loading serverops.properties file 
     properties.load(new FileInputStream(PROPERTIES_FILENAME)); 
    } catch (IOException e) { 
     System.out.println("Error loading "+PROPERTIES_FILENAME+" properties!"); 
     e.printStackTrace(); 
    } 

    try { // loading jdbc driver 
     Class.forName("com.mysql.jdbc.Driver"); 
    } catch (ClassNotFoundException e) { 
     System.out.println(" JDBC Driver not found"); 
     e.printStackTrace(); 
     return; 
    } 

    System.out.println("MySQL JDBC Driver found! Connecting to Database"); 
    Connection connection = null; 

    ResultSet resultSet = null; 
    ResultSet findResultSet = null; 

    PreparedStatement allStatement, findStatement, updateStatement; 

    //date formate used yyyy/MM/dd HH:mm:ss 
    DateTimeFormatter dateFromatter= DateTimeFormat.forPattern(DATE_FORMAT); 

    DateTime startDate = dateFromatter.parseDateTime(properties.getProperty(PROPERTY_NAME_STARTTIME)); 
    DateTime endDate= dateFromatter.parseDateTime(properties.getProperty(PROPERTY_NAME_ENDTIME)); 

    Timestamp t1 = new Timestamp(startDate.getMillis()); 
    Timestamp t2 = new Timestamp(endDate.getMillis()); 

    StringBuilder sql;  
    String toBeRemovedIndex ; 
    int updateNumEntries; 
    String toBeRemovedLog; 
    String toBeRemovedHostname; 
    boolean updateDB=false; 

    try { // connecting to DB with credentials from the properties file 
     connection = DriverManager.getConnection( 
     "jdbc:mysql://localhost:3306/"+properties.getProperty(PROPERTY_NAME_DB_NAME), 
     properties.getProperty(PROPERTY_NAME_DB_USERNAME), 
     properties.getProperty(PROPERTY_NAME_DB_PASSWORD)); 

    // getting all record between start time and end time 
     sql = new StringBuilder("SELECT * FROM" 
          + properties.getProperty(PROPERTY_NAME_DB_TABLENAME) 
          + " WHERE last_seen BETWEEN ? AND ?"); 

     allStatement = connection.prepareStatement(sql.toString()); 
     allStatement.setTimestamp(1, t1); 
     allStatement.setTimestamp(2,t2); 
     resultSet = allStatement.executeQuery(); 

     while (resultSet.next()) { 
     toBeRemovedIndex = resultSet.getString(1); 
     updateNumEntries = resultSet.getInt(2); 
     toBeRemovedLog = resultSet.getString(3); 
     toBeRemovedHostname = resultSet.getString(5); 

     // selecting the duplicate entries with logmessage , id and hostname 
     sql = new StringBuilder("SELECT * FROM " 
           + properties.getProperty(PROPERTY_NAME_DB_TABLENAME) 
           + " where log_message=? and id <> ? and hostname = ?" ); 

     findStatement = connection.prepareStatement(sql.toString()); 
     findStatement.setString(1, toBeRemovedLog); 
     findStatement.setString(2, toBeRemovedIndex); 
     findStatement.setString(3, toBeRemovedHostname); 

     findResultSet = findStatement.executeQuery(); 
     String newId=""; 
     while(findResultSet.next()) { 
      newId = findResultSet.getString(1); 
      updateNumEntries +=findResultSet.getInt(2); 
      updateDB = true; 
     } 

     if(updateDB) { // if duplicate entry found - deleting it from the db 
      sql = new StringBuilder("DELETE FROM" 
           + properties.getProperty(PROPERTY_NAME_DB_TABLENAME) 
           + " where id = ?"); 
      updateStatement = connection.prepareStatement(sql.toString()); 
      updateStatement.setString(1, toBeRemovedIndex ); 
      updateStatement.executeUpdate(); 

      // updating similar entry with number of records 
      sql = new StringBuilder("Update " 
           + properties.getProperty(PROPERTY_NAME_DB_TABLENAME) 
           + " set number_of_entries = ? where id = ? "); 

      updateStatement = connection.prepareStatement(sql.toString()); 
      updateStatement.setLong(1, updateNumEntries); 
      updateStatement.setString(2, newId); 
      updateStatement.executeUpdate(); 
      updateDB = false; 
      updateStatement=null; 
     } 
     } 
    } catch (SQLException e) { 
     System.out.println("Connection Failed! Check output console"); 
     e.printStackTrace(); 
     return; 
    } 

    DateTime now = new DateTime(); 

    if(connection != null) { 
     try { 
     connection.close(); 
     } catch (SQLException e) { 
     e.printStackTrace(); 
     } 
    } 

    System.out.println("Current time = " 
         + now.toString() 
         + " DB consoladiation done successfully between " 
         + t1.toString() + " and "+ t2.toString()); 
    } 
} 

테이블 구조는 :

CREATE TABLE IF NOT EXISTS `syslog_parsed` (
    `id` char(36) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, 
    `number_of_entries` int(11) NOT NULL, 
    `log_message` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, 
    `last_seen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
    `hostname` varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, 
    KEY `ID` (`id`) 
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 
+0

더 이상 필요하지 않을 때 명령문과 결과 세트를'close '하면 차이가 있는지보십시오. – Thilo

+0

아래 나의 대답을 참조하십시오. 바라건대 올바른 방향으로 나아갈 수 있기를 바랍니다. –

답변

2

기본적인 문제는 당신이 루프 동안 중첩 된 것입니다. 내부 루프는 100k 항목의 테이블에서 SQL 쿼리를 수행합니다. 즉, 100,000 개의 SQL 호출을 포함하여 100,000^2 개의 연산을 수행해야합니다. 또한 호출이 닫히지 않으므로 Java는 결과 집합을 메모리에 유지합니다. 따라서 heapsize 오류가 발생합니다.

코드의 대부분은 잔인하며 SQL 문으로 생략 할 수 있습니다. (더 이상 코드는 중복 처리해야하므로이 훨씬 더 빨리 실행해야

String sql = "SELECT id FROM (" 
      + " SELECT id, log_message, hostname FROM syslog_parsed " 
      + " WHERE last_seen BETWEEN ? AND ? " 
      + " GROUP BY id, log_message, hostname " 
      + ") x "//creates a subtable with grouped items 
      + "GROUP BY id " 
      + "HAVING count(*) > 1"//only returns rows that appear more than once 

PreparedStatement stmt = connection.prepareStatement(sql.toString()); 
stmt.setTimestamp(1, t1); 
stmt.setTimestamp(2, t2); 
resultSet = stmt.executeQuery(); 

while (resultSet.next()) { 
    //Copy result 
    //Delete all of that result 
    //Add back in one such result 
} 

이 꽤 잘하지 않을 수 있습니다, 그래서 당신은 당신의 데이터베이스에 대해 테스트해야하지만, 그것은 당신에게 핵심 요소를 제공합니다 아마, 50k 레코드보다 훨씬 적을 것이다). 그것은 sql에 중복을 찾는 작업을 덜어 주는데, 이는 이런 종류의 작업에 매우 좋습니다.

미래를위한 참고 사항 : 코드를 포맷하십시오. 그렇지 않으면 읽기가 어려워집니다. 또한 실제로 중요한 코드를 집중적으로 시도해 볼 수도 있습니다.

+0

포인터를 주셔서 고맙습니다 - 뇌가 얼어 버릴 것 같아요 - 내 마음에 와야 겠어 - 내가 부적절한 JDBC 프로그래밍을위한 것이라고 생각 했어 :) 나는 그것을 시험해보고 당신에게 알려준다 –

+0

문제. 그리고하지 않으면 대답을 받아들입니다! ;-) –