본문 바로가기

Dev.../플밍 관련 자료

[펌] Java JDBC Performance Tip(2)-Insert, Delete Speed up!

www.javapattern.info 에서 퍼왔습니다.

 

========================================================================================

이전 아티클에서는 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메소드인
것이다.

첨보았는가? 아니면 아래의 BMT아티클에서도 보았는가?
Other--> Development를 보게 되면 Java vs Pro*C의 비교자료가 있다. 한번읽어보기 바라며 그 내용의
상세부분을 간략한 코딩으로서 당신에게 보여주고자 한다.

기본적으로 java에서도 그러한 배치기능을 사용하고자 한다고 하고, 당신이 만약 드라이버 개발자라고 하면
어떻게 데이터베이스에 작업을 할것인가?

그나마 setAutoCommit(false)를 connection에 때려넣어놓으면 commit에 대한 그만큼의 비용이 떨어질것이라는
걸 알고 있다면 다행이겠다. 자. 그러면 데이터를 소위 "밀어넣는다"라고 이야기했을 때 가장 최선책은?
건당 하는 건 무리일테고, 음~ 자바니까 Collection을 이용해보면 딱이겠다.

그리고 Collection을 이용한다면 들어오는 데이터는 중복을 허용하고 순서도 있어야 할테니까
List 계열을 사용하는 것을 좋겠고.. 놀새의 결론은 List중에서도 제일 퍼포먼스가 좋은 ArrayList를
사용하는게 딱일것이라고 생각했다. 하지만 실제 Driver는 어떠한 자바버젼에서도 맞아야 하기 때문에
1.2부터 사용되는 ArrayList는 좀 고려해볼만도 하다.

그러면 뭐가 들어오는 sql문장에 대한 저장소로 적당할 것인가? 답은 Vector!! 왜 Vector인지는 설명하지
않겠다. 이정도 글읽는 당신정도라면 API는 어느정도 숙지하고 있을 거라는 놀새의 생각때문이다.

우선 그러면 실제 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;            }        }    }


위에서 보면 당연히 connection은 동기화되어져야 하므로 synch걸어놓고 작업할게 뻔하며,
parseExecuteFetchWithTimeout메소드가 실제 update작업을 이루게끔 하는데 저놈은 timeout이
걸려있으면 타이머 작동시키고 statement에 update때리는 작업을 하며, timeout이 0이면
바로 update이다. 복잡한가?

PreparedStatement(이하 PS)의 addBatch()메소드는 조금 더 복잡하므로 간략하게 어떻게
작동되는지만 설명하겠다.
PS의 경우는 쿼리가 이미 database의 내부 procedure로 변환되어져 있기 때문에 Stream을 이용하여
데이터를 세팅시키는 일을 한다는 것이 Statement Batch와 틀린 점이다. 그렇다면 Statement와
PreparedStatement의 속도차이는 얼마나 될까? 놀새는 이미 전부 다 해봤기 때문에 이건 당신의
숙제로 남겨두겠다.

위의 내용복잡하면 몰라도 됨을 강력히 주장한다. 당신이 JDBC Driver개발자가 아니지 않은가~!! ^^

자 그럼 이제 코더의 신분으로 돌아왔다고 가정을 하고, API를 이용하여 코딩이나 한번 해보자.

단순히 API를 사용하는 것이므로 거창하게 설명하고 자시고 할 필요도 없이 바로 들어간다.
알아서 보라~

public class BatchTest {    private Connection getConnection(){        // 알아서 연결들 하라~! ^^    }        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);        }    }}


위에서 유심히 볼거라고는 bold체로 쓰여진 부분의 내용뿐이다. 위의 내용은 데이터베이스에서
추출된 데이터를 다시 계산하여 다른 데이터베이스 테이블에 insert하는 내용이며,
ArrayList에 담긴 대량의 데이터를 iteration하며 batch를 실행하는 것이다.

좀 더 자세한 메소드 설명은 API의 내용을 참조해도 무방할 듯 싶다.

addBatch()처음보는가? 그러면 다른 클래스를 예로 들어서 java.lang패키지 클래스의 메소드는
자유자재로 구사할 줄 아는가?

놀새가 아는 사람들끼리의 고수에 대한 표현은 다음과 같다.

"저 사람은 걸어다니는 API야~!"

아무렇지도 않은 것 같으면서도 얼마나 함축적인 표현인가~!! ( 나만 그런가? ㅎㅎ)
API를 이용하여 쉽게 만들수 있는데도 예전 C코딩처럼 함수 열나 만들어서 잘 만들었다고 자랑해봐짜
위의 걸어다는 API한테 망신당할 수 있다는 걸 명심해야 한다.

중요하게 쓰일수 있는 것은 바로 옆에 있으며, 당신눈에 띄지 않는 것이 대부분이다.
두 눈 크게 뜨고 다녀야 한다. API가 바로 그것인 것이다.



아참. 그리고 위의 코드같은거 테스트할때 웬만하면 TestCase만들어서 작성하십쇼. 습관이 중요합니다.
단위테스트의 중요성은 나중에 칼럼쓸일 있으면 쓰도록 하겠습니다.