공공부하자개발 · 영어 학습 노트
SQL
대용량·배치실행 계획·조인·인덱스·파티션·락·대량 처리0/8 완료
  • 01실행 계획 읽기
  • 02조인 방식: Nested Loop, Hash, Sort Merge 와 드라이빙 테이블
  • 03인덱스 튜닝
  • 04통계 정보와 힌트
  • 05파티션: 범위·목록·해시 분할, 파티션 프루닝, 파티션 단위 관리
  • 06대량 INSERT·UPDATE 와 배치 커밋
  • 07락과 데드락
  • 08대용량 삭제·아카이빙
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 대용량·배치 › 06 / 8

대량 INSERT·UPDATE 와 배치 커밋

집합 처리, 나눠서 커밋, JDBC 배치
섹션 6진행 0 / 8
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6정리‹ 이전다음 ›

3. 코드 예제

소스: sql-src/batch_06_bulk_dml/01_set_based.sql, 02_chunked.sql, 03_verify.sql.

이 절의 sql 은 모두 H2 로 실행한 평문 SQL 입니다. 원천은 staging 4,000행이고 재귀 CTE 로 만들었으며, 대상은 target 입니다. H2 평문 SQL 에는 반복문이 없어서 나눠 처리하는 문장을 직접 2~3번 되풀이해 "한 번에 N건씩 끝까지" 흐름을 보입니다.

예제 1: 한 행씩 INSERT 와 INSERT ... SELECT

한 행씩 넣는 형태는 아래와 같습니다. 예시로 3건만 넣었고, 실제 프로그램에서는 이 문장을 애플리케이션 루프가 4,000번 반복합니다.

sql
INSERT INTO target_row (id, cust_id, amt, status_code, created) VALUES (1, 2, 200, 'B', DATE '2025-01-02');
INSERT INTO target_row (id, cust_id, amt, status_code, created) VALUES (2, 3, 300, 'C', DATE '2025-01-03');
INSERT INTO target_row (id, cust_id, amt, status_code, created) VALUES (3, 4, 400, 'A', DATE '2025-01-04');
text
ROW_BY_ROW_CNT
--------------
3
(1행)

같은 일을 한 문장으로 하면 4,000건이 한 번에 들어갑니다.

sql
INSERT INTO target (id, cust_id, amt, status_code, created)
SELECT
       id
     , cust_id
     , amt
     , status_code
     , created
  FROM staging;
text
TARGET_CNT
----------
4000
(1행)

문장 수는 4,000 대 1 이고 왕복·파싱·커밋도 그만큼 줄어듭니다. 이 레슨은 속도 숫자를 재지 않으므로 건수만 확인합니다.

예제 2: 여러 행 VALUES 와 집합 UPDATE

값을 직접 넣을 때는 여러 행 VALUES 로 한 문장에 씁니다. H2 에서 되는 형태이고 MySQL·MSSQL(2008 이상)에서도 같은 문법입니다.

sql
INSERT INTO code_map (code, label) VALUES
  ('A', '정상')
, ('B', '보류')
, ('C', '취소');
text
CODE | LABEL
-----+------
A    | 정상
B    | 보류
C    | 취소
(3행)

집합 UPDATE 는 CASE 로 여러 규칙을 한 문장에 담습니다. 금액에 따라 등급을 매기고 취소 건은 수수료를 0 으로 합니다.

sql
UPDATE target
   SET grade = CASE WHEN amt >= 7000 THEN 'A' WHEN amt >= 3000 THEN 'B' ELSE 'C' END
     , fee = CASE WHEN status_code = 'C' THEN 0 WHEN amt >= 7000 THEN amt / 100 ELSE amt / 50 END
 WHERE grade IS NULL;

4,000행이 한 문장으로 바뀌었고, 등급별로 묶어 확인합니다.

sql
SELECT
       grade
     , COUNT(*) AS cnt
     , SUM(fee) AS fee_sum
  FROM target
 GROUP BY grade
 ORDER BY grade;
text
GRADE | CNT  | FEE_SUM
------+------+--------
A     | 1148 | 63961
B     | 1640 | 108194
C     | 1212 | 24194
(3행)

1148 + 1640 + 1212 는 4,000 이고, grade IS NULL 인 행은 0건입니다. WHERE 의 grade IS NULL 은 이미 처리된 행을 건너뛰게 해 재실행을 안전하게 만듭니다.

예제 3: 키 범위로 나눠 INSERT

이제 4,000건을 1,500건씩 나눠 적재합니다. 범위는 id > :last AND id <= :last + 1500 이고, H2 평문 SQL 에서는 :last 자리에 값을 직접 씁니다. 청크마다 진행 표에 마지막 키를 기록하고 COMMIT 합니다.

sql
CREATE TABLE batch_progress (job_name VARCHAR(20) PRIMARY KEY, last_id INT, done_cnt INT);
INSERT INTO batch_progress VALUES ('load_target', 0, 0);

청크 1 은 id 가 0 초과 1500 이하인 행입니다.

sql
INSERT INTO target (id, cust_id, amt, status_code, created)
SELECT id, cust_id, amt, status_code, created FROM staging WHERE id > 0 AND id <= 1500;
UPDATE batch_progress SET last_id = 1500, done_cnt = done_cnt + 1500 WHERE job_name = 'load_target';
COMMIT;
text
LAST_ID | DONE_CNT
--------+---------
1500    | 1500
(1행)

청크 2 는 시작점을 진행 표의 last_id(1500)로 잡아 1500 초과 3000 이하를 넣고 같은 방식으로 기록합니다.

text
LAST_ID | DONE_CNT
--------+---------
3000    | 3000
(1행)

청크 3 은 3000 초과 4500 이하이지만 원천에는 4000번까지만 있어 남은 1,000건이 들어갑니다. 마지막 청크가 작아도 같은 문장으로 끝납니다.

text
LAST_ID | DONE_CNT
--------+---------
4500    | 4000
(1행)

여기서 last_id 는 상한 값(4500)이고 done_cnt 는 실제로 처리한 건수입니다. 세 청크가 끝난 뒤 대상 표를 확인합니다.

sql
SELECT COUNT(*) AS target_cnt, MIN(id) AS min_id, MAX(id) AS max_id FROM target;
text
TARGET_CNT | MIN_ID | MAX_ID
-----------+--------+-------
4000       | 1      | 4000
(1행)

실제 프로그램에서는 이 세 묶음이 반복문의 한 회차이고, 청크가 0건이 되면 반복이 끝납니다.

예제 4: 처리 표시 컬럼으로 미처리분만

이번에는 target 의 processed_yn 컬럼으로 미처리(N)만 1,500건씩 갱신합니다. 갱신할 행의 키를 서브쿼리로 골라 FETCH FIRST 로 건수를 제한하고, 갱신과 동시에 processed_yn 을 'Y' 로 바꿉니다.

sql
UPDATE target
   SET grade = CASE WHEN amt >= 7000 THEN 'A' WHEN amt >= 3000 THEN 'B' ELSE 'C' END
     , processed_yn = 'Y'
 WHERE id IN (
       SELECT id
         FROM target
        WHERE processed_yn = 'N'
        ORDER BY id
        FETCH FIRST 1500 ROWS ONLY
       );
COMMIT;

시작 전 미처리는 4,000건이었습니다. 같은 문장을 세 번 되풀이하며 청크마다 남은 건수를 봅니다.

text
TODO_BEFORE
-----------
4000
(1행)
text
TODO_AFTER_CHUNK1
-----------------
2500
(1행)
text
TODO_AFTER_CHUNK2
-----------------
1000
(1행)

세 번째 청크는 남은 1,000건만 갱신하고 끝납니다.

text
TODO_AFTER_CHUNK3
-----------------
0
(1행)

미처리가 0건이 되면 반복을 끝냅니다. 이 방식은 중간에 멈춰도 processed_yn 이 진행 상황을 가지고 있어서, 같은 문장을 다시 돌리기만 하면 이어집니다.

끝난 뒤 같은 UPDATE 를 다시 돌려도 미처리 건수(rerun_todo)는 0 이라 처리할 행이 없어서 아무것도 바뀌지 않습니다. 멱등성이 지켜진 것입니다.

예제 5: 처리 후 대사와 누락분 재적재

일부러 마지막 청크가 빠진 채 끝난 상태를 만들었습니다. 3,500건까지만 적재된 상태에서 건수와 금액 합계를 대조합니다. 실무 09 이관·검증의 대사 방식과 같습니다.

sql
SELECT
       (SELECT COUNT(*) FROM staging) AS src_cnt
     , (SELECT COUNT(*) FROM target) AS tgt_cnt
     , (SELECT SUM(amt) FROM staging) AS src_amt
     , (SELECT SUM(amt) FROM target) AS tgt_amt;
text
SRC_CNT | TGT_CNT | SRC_AMT  | TGT_AMT
--------+---------+----------+---------
4000    | 3500    | 19517200 | 17115200
(1행)

건수와 합계가 모두 다르므로 대상에 빠진 행이 있습니다. 누락 건수는 NOT EXISTS 로 셉니다.

sql
SELECT COUNT(*) AS missing_cnt
  FROM staging s
 WHERE NOT EXISTS (SELECT 1 FROM target t WHERE t.id = s.id);
text
MISSING_CNT
-----------
500
(1행)

누락분만 다시 적재합니다. 이미 있는 키는 NOT EXISTS 로 건너뛰므로 이 문장은 여러 번 돌려도 같은 결과입니다.

sql
INSERT INTO target (id, cust_id, amt, status_code, created)
SELECT s.id, s.cust_id, s.amt, s.status_code, s.created
  FROM staging s
 WHERE NOT EXISTS (SELECT 1 FROM target t WHERE t.id = s.id);

500건이 들어갔고 다시 대사합니다.

text
SRC_CNT | TGT_CNT | SRC_AMT  | TGT_AMT
--------+---------+----------+---------
4000    | 4000    | 19517200 | 19517200
(1행)

키 중복 건수(dup_keys), processed_yn = 'N' 인 행 수(todo_cnt)와 grade IS NULL 인 행 수(grade_null)도 모두 0 이었습니다. 대사 통과의 기준은 건수·합계 일치, 누락 0, 중복 0, 미처리 0 입니다.

예제 직접 실행

아래 폴더의 SQL 파일을 Git Bash 에서 H2 메모리 DB 로 실행합니다. 방언은 파일 안의 SET MODE 로 바꿉니다.

cd sql-src/batch_06_bulk_dml
ls *.sql
bash ../run.sh <파일>.sql
코드 예제
  • 예제 1: 한 행씩 INSERT 와 INSERT ... SELECT
  • 예제 2: 여러 행 VALUES 와 집합 UPDATE
  • 예제 3: 키 범위로 나눠 INSERT
  • 예제 4: 처리 표시 컬럼으로 미처리분만
  • 예제 5: 처리 후 대사와 누락분 재적재
이전 섹션2 핵심 원리3 / 6다음 섹션4 응용 변형 예제