Back-End/DB

데이터베이스에 대량 데이터를 Insert 할 때, 속도 차이가 나는 이유

김검정 2025. 6. 16. 10:59

데이터베이스에 대량의 데이터를 삽입(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초 가장 빠름 (단, 가변 데이터 처리에 어려움이 있다)