웹 개발

왜 JPA에서 @DynamicInsert를 쓰면 Statement 캐싱 효율이 떨어질까?

ultramancode 2023. 2. 10. 02:02

1. 일반 INSERT vs. Dynamic Insert

케이스 실제로 DB에 보내지는 SQL
기본(JPA 기본값) sql INSERT INTO user (id, name, created_at) VALUES (?, ?, ?)
모든 컬럼을 명시하고, null 값이면 ? 자리에 NULL이 넘어감
@DynamicInsert 적용 상황 ① created_at 이 null
sql INSERT INTO user (id, name) VALUES (?, ?)

상황 ② created_at 이 이미 세팅됨
sql INSERT INTO user (id, name, created_at) VALUES (?, ?, ?)
 
  • 컬럼마다 값이 null 인지 아닌지에 따라 SQL 문자열 자체가 달라진다.

2. Statement 캐싱이란?

  1. JDBC 드라이버(또는 커넥션 풀/Hibernate)는 SQL 문자열을 키로 PreparedStatement를 캐싱
  2. 같은 SQL이 다시 오면 서버 · 클라이언트 모두 “파싱 → 플랜 생성” 단계를 건너뛸 수 있어 속도와 CPU가 절약
[SQL 문자열] --------------> [PreparedStatement 캐시] 재사용 (같은 문자열일 때만!)

3. 캐싱 효율 ↓ : 이유

  • @DynamicInsert 때문에
    • INSERT INTO user (id, name) VALUES ...
    • INSERT INTO user (id, name, created_at) VALUES ...
    • (null 컬럼 조합이 더 있으면 더 많은 변형…)
  • SQL 문자열이 매번 달라지므로 캐시 미스가 발생 → 매 INSERT마다
    1. 파싱
    2. 실행 계획 생성
    3. PreparedStatement 객체 새로 생성
      ⇒ 미세하지만 빈번하면 누적 비용이 커짐

특히 대량 배치 INSERT나 TPS가 높은 서비스에서는 차이가 눈에 보일 수도 있다고 한다!

 


4. 그럼 써도 되나, 말아야 하나?

 

판단 기준 권장
DB 디폴트값 활용 빈도 Audit 컬럼(now, uuid)처럼 항상 DB에서 자동 채워지는 값이면 @DynamicInsert 를 쓰지 않고 애초에 코드에서 값을 빼도 됨 -> now(), uuid() 등 DB에서 디폴트로 넣어주는건 SQL 고정 -> 캐싱 
INSERT 빈도 트래픽 낮고 디폴트 컬럼이 많으면 @DynamicInsert 사용 OK -> 성능 하락은 미미하지만 코드 수정 없이 DB 디폴트 쓰는 편안함은 크기 때문
성능 민감 대량 INSERT · 고TPS라면 정적 SQL 추구하기(캐싱 100%) + 코드에서 null/기본값 처리 권장

예시)

// 1) 트래픽 높음. createdAt 값을 코드에서 직접 넣어준다.
public User create(String name) {
    User u = new User(name, LocalDateTime.now()); // createdAt 채움
    return userRepo.save(u);                      // @DynamicInsert 없이도 OK (고정 SQL)
}

// 2) 트래픽 낮음. DB가 알아서 채우게 두고 싶다.
@Entity 
@DynamicInsert
public class User {
    @ColumnDefault("now()")
    private LocalDateTime createdAt; // 코드에서 값 넣지 않음(null)
}

최종 정리를 하자면

  • @DynamicInsert는 null 컬럼을 빼서 DB 디폴트 쓰자는 편리함을 주지만,
  • SQL 문장이 경우의 수만큼 달라져 캐시 재사용률이 떨어질 수 있다.
  • 빈번한 INSERT + 성능 민감 상황이면, 디폴트값을 코드에서 직접 세팅하는 것도 나쁘지 않다!