벌써 작성한지 13년이나 된 글이네요. 추억 겸 올려봅니다.
Java JDBC Performance Tip-Insert, Delete Speed up!
이전 아티클에서는 database select speedup에 대하여 알아보았다. 이번 편에서는 update를 제외한 insert, delete tranaction의 속도를 최대로 끌어올릴 수 있는 방안을 살펴봅니다.
▶ Insert, Delete Query speed up!
JDBC 2.0에서는 기본적으로 scrollable한 메소드등의 유연성있는 메소드들과 batch processing에 관련된 메소드가 추가되었습니다.
그게 바로 어떤 것이냐하면 Statement, PreparedStatement 인터페이스의 addBatch(String sql), addBatch()메소드입니다. Pro*C같은 경우 기본적으로 array processing을 이용하여 데이터베이스에
작업을 하므로 상당한 퍼포먼스를 낼 수 있는데 이를 자바측으로 변환한것이 바로 addBatch메소드인 것이죠.
기본적으로 java에서도 배치기능을 사용하고자 한다고 하고, 여러분이 만약 드라이버 개발자라고 하면 어떻게 데이터베이스에 작업을 할까요?
그나마 setAutoCommit(false)를 connection에 때려넣어놓으면 commit에 대한 그만큼의 비용이 떨어질 것이라는걸 알고 있다면 다행입니다. 자. 그러면 데이터를 소위 "밀어넣는다"라고 이야기했을 때 가장 최선책은 무엇일까요? 건당 하는 건 무리일테고, 음~ 자바니까 Collection을 이용해보면 적당하겠네요.
그리고 Collection을 이용한다면 들어오는 데이터는 중복을 허용하고 순서도 있어야 할테니까 List 계열을 사용하는 것을 좋겠고 놀새의 결론은 List중에서도 제일 퍼포먼스가 좋은 ArrayList를 사용하는게 딱일것이라고 생각했습니다. 하지만 실제 Driver는 어떠한 자바버젼에서도 맞아야 하기 때문에 1.2부터 사용되는 ArrayList는 좀 고려해 볼만도 합니다.
그러면 뭐가 들어오는 sql문장에 대한 저장소로 적당할까요? 답은 Vector!! 왜 Vector인지는 설명하지 않겠습니다다.
우선 그러면 실제 Oracle을 예로 driver내부나 한번 보도록 할까요?
JDBC API Statement 인터페이스의 구현체인 OracleStatement코드의 addBatch메소드를 잠깐 보도록 하죠.
public synchronized void addBatch(String s) throws SQLException { addBatchItem(s); } private void addBatchItem(String s) { m_batchItems.addElement(s); }addElement를 사용하는 걸 보니 놀새가 생각했던 것처럼 Vector를 사용하고 있습니다. 즉 batch를 위한 저장소로서 Vector class를 사용중인 것이다. 그러면 Statement클래스의 실제 batch execute 명령어인 executeBatch()메소드를 보도록 하죠.
public int[] executeBatch() throws SQLException { synchronized(connection) { synchronized(this) { int i = 0; int j = getBatchSize(); if(j <= 0) { int ai[] = new int[0]; return ai; } int ai2[] = new int[j]; Object obj = null; Object obj1 = null; Object obj2 = null; boolean flag = false; ensureOpen(); prepare_for_new_result(true); try { connection.needLine(); for(i = 0; i < j; i++) { String s = getBatchItem(i); String s1 = expandSqlEscapes(s); byte abyte0[] = strToDbaccessBytes(s1); byte byte0 = getSqlKind(s1); if(byte0 == 0) DBError.throwBatchUpdateException(80, "invalid SELECT batch command " + i, i, ai2); ai2[i] = parseExecuteFetchWithTimeout(dbstmt, byte0, abyte0, null, 1, null, 1); if(ai2[i] < 0) DBError.throwBatchUpdateException(81, "command return value " + ai2[i], i, ai2); } } catch(IOException ioexception) { DBError.throwBatchUpdateException(81, ioexception.getMessage(), i, ai2); } catch(SQLException sqlexception) { if(sqlexception instanceof BatchUpdateException) throw sqlexception; DBError.throwBatchUpdateException(81, sqlexception.getMessage(), i, ai2); } finally { clearBatchItems(); } int ai1[] = ai2; return ai1; } } }
public class BatchTest { private Connection getConnection(){ // DB 연결하세요 } private void close() { // 연결 닫으세요 } public void insertABT231Batch(){ System.out.println(Utility.getTime() + " Insert ABT231 Batch Start .. Transaction size is " + m_abt231InsertList.size()); Connection conn = null; PreparedStatement pstmt = null; try { StringBuffer query = new StringBuffer(); query.append("INSERT INTO ABT231 "); query.append("(customer_no, item_cd, occur_amt, reason_cd, register_ymd, register_no) "); query.append(" VALUES (?, ?, ?, '9', ?, ?) "); conn = getConnection(); conn.setAutoCommit(false); pstmt = conn.prepareStatement(query.toString()); Iterator iter = m_abt231InsertList.iterator(); int count = 0; while( iter.hasNext() ) { m_abt231 = (Abt231) iter.next(); pstmt.setInt(1, m_abt231.getCustomerNo()); pstmt.setString(2, m_abt231.getItemCd()); pstmt.setLong(3, m_abt231.getOccurAmt()); pstmt.setString(4, s_magamCurrentTime); pstmt.setInt(5, Integer.parseInt(s_workCd)); pstmt.addBatch(); count++; if( (count % 10000) == 0){ System.out.println(count + "건 처리중"); pstmt.executeBatch(); } } pstmt.executeBatch(); conn.commit(); System.out.println(Utility.getTime() + "] " + count + "건 입력완료"); } catch ( Exception e) { e.printStackTrace(); try{ conn.rollback(); }catch(Exception e2) {e2.printStackTrace();} } finally { close(pstmt); close(conn); } } }