홈 › 대용량·배치 › 06 / 8

대량 INSERT·UPDATE 와 배치 커밋

집합 처리, 나눠서 커밋, JDBC 배치
섹션 6진행 0 / 8

4. 응용 변형 예제

예제 6: DB별 청크 문법 (문법 검토만)

N건만 처리하는 문법은 DB 마다 다릅니다. 아래 문장은 이 레슨에서 H2 로 실행하지 않았고 문법 검토만 했습니다. 처리 표시 컬럼으로 미처리 1,000건을 갱신하는 예입니다.

sql
-- Oracle · Tibero
UPDATE target
   SET processed_yn = 'Y'
 WHERE processed_yn = 'N'
   AND ROWNUM <= 1000;

Oracle 12c 부터는 예제 4 처럼 FETCH FIRST 를 서브쿼리에 쓸 수도 있습니다. MySQL 은 UPDATE 에 LIMIT 을 바로 씁니다.

sql
-- MySQL
UPDATE target
   SET processed_yn = 'Y'
 WHERE processed_yn = 'N'
 ORDER BY id
 LIMIT 1000;

MSSQL 은 UPDATE 뒤에 TOP 을 씁니다.

sql
-- MSSQL
UPDATE TOP (1000) target
   SET processed_yn = 'Y'
 WHERE processed_yn = 'N';

MSSQL 의 UPDATE TOP 은 어떤 행이 뽑히는지 정해 주지 않습니다. 순서가 필요하면 키를 골라 조인하는 식으로 씁니다. 청크 크기는 잠금 에스컬레이션이 생기는 약 5,000개보다 작게 두기도 합니다.

예제 7: JDBC 배치와 배치 커밋

애플리케이션에서 여러 건을 넣을 때는 JDBC 배치를 씁니다. setAutoCommit(false) 로 자동 커밋을 끄고, addBatch 로 모아 1,000건마다 executeBatch 와 commit 을 합니다. 마지막 잔여분도 처리하고, 예외가 나면 rollback 합니다.

java
String sql = "INSERT INTO target (id, amt) VALUES (?, ?)";
try (Connection con = ds.getConnection();
     PreparedStatement ps = con.prepareStatement(sql)) {
    con.setAutoCommit(false);
    try {
        int n = 0;
        for (Row r : rows) {
            ps.setInt(1, r.id());
            ps.setInt(2, r.amt());
            ps.addBatch();
            if (++n % 1000 == 0) {
                ps.executeBatch();
                con.commit();
            }
        }
        ps.executeBatch();   // 남은 잔여분
        con.commit();
    } catch (SQLException e) {
        con.rollback();      // 마지막 커밋 이후만 취소
        throw e;
    }
}

executeBatch 는 모아 둔 문장을 한 번에 보내 왕복을 줄입니다. rollback 은 마지막 commit 이후의 청크만 취소하므로, 앞선 청크는 남습니다. 그래서 재시작 지점과 멱등성이 필요합니다.

MySQL 은 JDBC URL 에 rewriteBatchedStatements=true 를 붙이면 배치를 여러 행 INSERT 로 합쳐 보냅니다. MyBatis 는 SqlSession 을 ExecutorType.BATCH 로 열면 같은 방식으로 배치가 됩니다.

예제 8: 청크 흐름과 대량 적재 도구 (도식과 문법 검토만)

나눠서 처리하는 반복문의 흐름은 아래와 같습니다.

도식(H2 미지원, 실행 결과 아님)

text
시작 → 진행 표에서 last_id 읽기
  ↓
청크 처리: id > last_id AND id <= last_id + N
  ↓
처리 건수 0 이면 종료
  ↓
last_id 갱신 → COMMIT → 다음 청크

실패하면 프로그램을 다시 시작해 진행 표의 last_id 부터 이어 갑니다. 이미 커밋된 청크는 남아 있고, 실패한 청크는 롤백되어 처음부터 다시 처리됩니다.

수백만 건 이상의 초기 적재는 SQL 문장보다 전용 도구가 빠릅니다. 아래는 문법 검토만 한 예입니다.

sql
-- Oracle · Tibero: 직접 경로 INSERT
INSERT /*+ APPEND */ INTO target
SELECT * FROM staging;
COMMIT;
sql
-- MySQL
LOAD DATA INFILE '/data/staging.csv' INTO TABLE target
FIELDS TERMINATED BY ',';

-- MSSQL
BULK INSERT target FROM 'C:\data\staging.csv'
WITH (FIELDTERMINATOR = ',', TABLOCK);

Oracle 직접 경로 INSERT 는 표를 잠그고, 커밋 전에는 같은 세션에서도 그 표를 조회할 수 없습니다. NOLOGGING 과 함께 쓰면 복구에 주의가 필요합니다. MSSQL 은 복구 모델이 SIMPLE 또는 BULK_LOGGED 이고 TABLOCK 을 쓰면 최소 로깅이 됩니다. Oracle PL/SQL 에는 BULK COLLECT 와 FORALL 이 있다는 정도만 알아 둡니다.