데이터베이스에 대량의 데이터를 삽입(insert) 할 때, 같은 데이터를 넣더라도 방식에 따라 속도 차이가 최대 5배 이상 나는 경우가 있다.
100만 건의 데이터를 DB에 넣는다고 가정해보자. (Oracle, PostgreSQL, MySQL 모두 해당된다)
설명 |
1. 단건 insert (1건씩 insert 수행, 오토커밋) |
2. 단건 insert + 수동 커밋 (트랜잭션 한 번에 묶음) |
3. PreparedStatement 반복 (JDBC 배치 처리) |
4. ORM (BatchSize 설정) |
5. Bulk insert SQL (insert into values (...), (....)) |
위와 같은 방식이 있을것이다. 각각의 방식이 왜 차이가 생기는지 정리를 해보자.
1. 단건 insert (1건씩 insert + 오토커밋)
for (int i = 0; i < 1000000; i++) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "User" + i);
pstmt.setString(2, "user" + i + "@example.com");
pstmt.executeUpdate(); // 매번 DB round-trip 발생
}
}
위 방식은
- 커넥션 매번 생성/종료 : 너무 느림
- 매번 오토커밋 : DB 트랜잭션 비용 증가
- I/O 부하 증가 : 디스크 flush가 반복됨
이런 단점들로 인해 가장 느리다(100만 건 기준 수 분 이상 소요될 수 있음)
2. 단건 insert + 수동 커밋 (트랜잭션 묶기)
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (name, email) VALUES (?, ?)");
for (int i = 0; i < 1000000; i++) {
pstmt.setString(1, "User" + i);
pstmt.setString(2, "user" + i + "@example.com");
pstmt.executeUpdate();
}
conn.commit();
위의 1번 방식보다는 개선되었다.
- 트랜잭션이 한 번만 발생 → 비용 절감
- 커넥션 재사용
- 단건 처리지만 I/O flush 횟수 감소
하지만 여전히 100만 건 DB 요청을 100만 번 한다.
3. JDBC Batch Insert
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (name, email) VALUES (?, ?)");
for (int i = 0; i < 1000000; i++) {
pstmt.setString(1, "User" + i);
pstmt.setString(2, "user" + i + "@example.com");
pstmt.addBatch();
if (i % 1000 == 0) {
pstmt.executeBatch();
pstmt.clearBatch();
}
}
pstmt.executeBatch(); // 남은 것 처리
conn.commit();
위 방식의 핵심은
- addBatch() 로 모아서 executeBatch()로 일괄처리
- DB round-trip이 100만 번 → 1,000번으로 줄어든다
- 트랜잭션도 한 번만 발생
그렇다면 성능은 개선되었을까?
- 일반적인 기본 insert의 3~5배 이상 빠름
- PostgreSQL, Oracle, MySQL 모두 지원한다
- 메모리 사용량만 주의가 필요하다 (Batch 크기가 너무 크면 OOM 발생 가능성이 있다)
4. ORM (JAP/Hibernate) + BatchSize 설정
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
}
기본 insert (JAP) :
for (int i = 0; i < 1000000; i++) {
User u = new User("User" + i, "user" + i + "@email.com");
entityManager.persist(u);
}
위 방식에서의 문제점은
- insert 쿼리 100만 번 발생
- flush/commit 지연 발생
- 트랜잭션이 너무 길어지면 성능 저하/락 발생
개선하는 방법은 Batch 설정이다
properties :
spring.jpa.properties.hibernate.jdbc.batch_size=1000
spring.jpa.properties.hibernate.order_inserts=true
→ Hibernate가 내부적으로 JDBC addBatch()를 호출하도록 해준다
이렇게 개선후의 성능은
- 거의 JDBC batch insert 수준까지 도달 가능하다
- ORM 코드 유지 + 성능 모두 확보가 가능하다
5. Raw SQL bulk insert (특정 DB 지원)
INSERT INTO users (name, email)
VALUES
('User1', 'user1@email.com'),
('User2', 'user2@email.com'),
('User3', 'user3@email.com');
-- 수천 개 VALUES 묶음
특징 :
- 가장 빠른 방식 (DB 엔진 최적화)
- 단점 : 가변 데이터 처리에 어려움이 있다
- 일반적으로 ORM에서는 사용하기 힘들다
- 대량 데이터 마이그레이션, 초기 로딩에 적합하다
마지막으로 실행 결과를 비교해보자
방식 | 실행 시간 | 특징 |
단건 insert (오토커밋) | 약 800초 | 최악 |
단건 insert + 수동 커밋 | 약 400초 | 약간 개선 |
JDBC batch insert | 약 90초 | 훨씬 빠름 |
JPA + batch 설정 | 약 110초 | 안정적 + 실무 적합 |
raw bulk insert | 약 40초 | 가장 빠름 (단, 가변 데이터 처리에 어려움이 있다) |
'Back-End > DB' 카테고리의 다른 글
Oracle SQL Developer 에서 쿼리 계획(EXPLAIN PLAN) 보는 방법 (0) | 2025.05.07 |
---|---|
오라클 날짜별 조회 (0) | 2025.04.07 |
오라클 ORA-00054 : 리소스가 사용 중이어서 NOWAIT가 지정되었거나 시간 초과가 만료된 상태로 획득합니다. (0) | 2025.04.03 |
오라클에서 정말 많이 사용하는 GROUP BY 졸업하기 (0) | 2025.03.12 |
오라클 MERGE INTO 구문과 INACTIVE (1) | 2024.10.18 |