@Transactional - 스냅샷은 첫 쿼리에 만드는데, 커넥션은 왜 미리 잡을까

2026. 4. 5. 18:28·프로그래밍

하기 블로그에서 동일한 컨텐츠를 제공합니다.

https://blog.giwon.dev/articles/2


 

 

@Transactional 메서드 안에 섞여 있던 외부 API 호출을 트랜잭션 밖으로 분리하다가, 문득 생각이 들었다.

 

스냅샷은 쿼리 첫 호출때 생성하면서 왜 커넥션은 미리 잡지?

 

MySQL의 기본 격리 수준인 REPEATABLE READ에서 스냅샷은 첫 번째 쿼리 시점에 생성된다.
그런데 커넥션은 @Transactional 메서드에 진입하는 순간, 쿼리를 하나도 실행하지 않았는데 이미 잡혀 있다.

AOP라서 메서드 진입 시점에 잡는 거 아닌가?

 

처음엔 그렇게 넘어가려 했다. 그런데 생각해보니 좀 이상했다.

스냅샷도 AOP 시점에 미리 만들어두면 될 텐데, 스냅샷은 첫 쿼리까지 기다린다.
같은 트랜잭션 안에서의 동작인데 커넥션은 미리 잡고, 스냅샷은 나중에 만든다...?

 

스냅샷은 나중에 잡으면서 커넥션은 미리 잡아야 하는 이유가 있는지 궁금해졌다. 그래서 한번 파보기로 했다.

 

코드 추적하기

커넥션이 잡히는 지점을 브레이크포인트로 두고, 실제로 어느 시점에 잡히는지 디버깅 해보았다. (참고: 카카오페이 기술블로그)

혹시 쿼리가 몰래 나가는 건 아닌가 싶어서, 메서드 안의 쿼리를 전부 비우고 테스트했다.
그런데도 브레이크포인트가 걸렸다. 쿼리와 무관하게, 메서드에 진입하는 것만으로 커넥션을 잡고 있었다.

TransactionInterceptor.invoke()             ← AOP 진입점
  → ...
    → LogicalConnectionManagedImpl.begin()  ← 여기서 커넥션을 잡음
      → HikariDataSource.getConnection()    ← 커넥션 획득

 

AOP가 트랜잭션을 시작하고, Hibernate가 begin()을 처리하는 과정에서 LogicalConnectionManagedImpl.begin()이 커넥션을 꺼내고 있었다.

 

범인은 autoCommit이었다

AOP로 인해 메서드 실행 전에 다음과 같은 코드가 호출된다.

// LogicalConnectionManagedImpl.java

initiallyAutoCommit = !doConnectionsFromProviderHaveAutoCommitDisabled()
    && determineInitialAutoCommitMode(getConnectionForTransactionManagement());

getConnectionForTransactionManagement() 한 줄이 쿼리 실행 전에 커넥션을 잡는 원인이었다.

 

왜 이렇게 하는 걸까?

JDBC 스펙상 커넥션의 기본 autoCommit 값은 true다. 이 상태에서는 각 SQL 문이 실행 즉시 커밋된다.

-- autoCommit = true 일 때
INSERT INTO user (name) VALUES ('A');  -- 즉시 커밋
INSERT INTO user (name) VALUES ('B');  -- 에러 발생하면? A는 이미 커밋되어 롤백 불가

Hibernate 입장에서는 커넥션 풀이 autoCommit을 어떻게 설정해뒀는지 모른다. 그래서 만약 autoCommit을 사용하고 있으면 false로 세팅하여 커넥션을 맺어야 한다.

그래서 AOP로 setAutoCommit 설정을 미리 하기 위해 커넥션을 잡는 것이다.

여기에 한 가지 더. Hibernate는 트랜잭션이 끝나면 autoCommit을 원래 상태로 복원한다.
커넥션은 풀에 반납되고, 다음에 이 커넥션을 꺼내 쓰는 쪽이 @Transactional 없이 사용할 수도 있으니까. 복원하려면 원래 상태를 getAutoCommit()으로 먼저 읽어야 한다.

 

정리하면 이 순서다.

1. 커넥션풀에서 커넥션 획득
2. getAutoCommit()으로 원래 상태 저장
3. setAutoCommit(false)
4. 쿼리 실행
5. commit() 또는 rollback()
6. setAutoCommit(원래값)으로 복원
7. 풀에 반납

2번과 3번 때문에, 쿼리와 무관하게 메서드 진입 시점에 커넥션을 잡는 것이었다.

 

그냥 첫 쿼리 시점에 setAutoCommit(false) 하면 안 되나?

별도 설정 없이도, 트랜잭션 시작 시점이 아닌 첫 쿼리 시점에 커넥션을 잡고 getAutoCommit() 저장 → setAutoCommit(false) 호출하면 되지 않을까?
이렇게 하면 쿼리 호출하지 않는 메서드에서 커넥션을 잡지 않을 수 있다.

왜 이 시점에 하는지에 대한 명확한 이유는 찾지 못했다.
Hibernate의 트랜잭션 모델이 "트랜잭션 시작 = 커넥션 획득"이라는 단순한 구조로 설계된 것이고, 이에 대해 autoCommit 체크만 생략하는 최소한의 옵션만 제공하는 것으로 보인다.

 

그래서 뭐가 문제인가

커넥션풀에서 커넥션을 꺼내는 건 이미 만들어진 TCP 연결을 인메모리에서 가져오는 것이라 빠르다.
문제는 언제 잡느냐가 아니라 얼마나 오래 들고 있느냐다.

@Transactional 메서드 안에 외부 API 호출이 섞여 있다고 해보자.
외부 API 응답이 3초 걸리면, 그 3초 동안 아무 쿼리도 날리지 않으면서 커넥션을 점유한다.

@Transactional  // ← 여기서 커넥션 획득
public void process() {
    externalApi.call();    // 3초 대기... 커넥션은 묶여 있음
    repository.save(data); // 실제 DB 사용은 여기서부터
}

사실 메서드에 Transactional을 적용할거라면, 커넥션 점유 여부를 떠나서 메서드 내에 외부 의존성을 함께 두지 않는 것을 선호한다.
코드를 읽을때 외부 의존성도 트랜잭션에 포함되어야 하는것처럼 보이니까... 명확한 표현을 위해 분리하는게 좋다고 생각한다.

 

더 심각한 케이스는 클래스 단위 @Transactional이다.
DB를 전혀 쓰지 않는 메서드에서도 커넥션을 점유한다. 이런 케이스가 동시에 쌓이면 HikariCP의 커넥션풀이 고갈된다.
connectionTimeout을 초과한 스레드는 블로킹되고, Tomcat 스레드풀까지 연쇄적으로 마비될 수 있다.
스냅샷은 첫 쿼리까지 기다리는데, 커넥션은 setAutoCommit 때문에 미리 잡힌다. 이 간극이 불필요한 커넥션 점유의 원인이다.

 

해결: 커넥션 획득을 첫 쿼리까지 미루기

Hibernate 5.2.10에서 HHH-11542로 추가된 설정이 있다.

spring:
  datasource:
    hikari:
      auto-commit: false                                        # 풀에서 autoCommit=false로 제공
  jpa:
    properties:
      hibernate.connection.provider_disables_autocommit: true    # Hibernate에게 알려줌

 

두 설정이 하는 일은 다음과 같다.

  • hikari.auto-commit: false → HikariCP가 커넥션을 꺼낼 때 이미 autoCommit=false 상태로 제공
  • provider_disables_autocommit: true → "풀이 이미 처리했으니 확인하지 마"라고 Hibernate에게 알려줌

Hibernate가 이를 신뢰하면 확인할 것도, 바꿀 것도, 복원할 것도 없으니 커넥션을 미리 잡을 이유가 사라진다.

Before: 메서드 진입 → 커넥션 획득 → getAutoCommit → setAutoCommit(false) → ... → 쿼리 → commit → 복원 → 반납
After:  메서드 진입 → ... → 첫 쿼리 시점에 커넥션 획득 → 쿼리 → commit → 반납

 

아까의 코드에 적용하면 3초 동안 커넥션을 낭비하던 것이 사라진다.

@Transactional  // ← 커넥션 아직 안 잡힘
public void process() {
    externalApi.call();    // 3초 대기... 커넥션 없음
    repository.save(data); // ← 이 시점에 커넥션 획득
}

주의: 두 설정은 반드시 같이 써야 한다

provider_disables_autocommit=true만 켜고 HikariCP의 auto-commit을 false로 안 바꾸면?
Hibernate는 setAutoCommit(false)를 호출하지 않는다.
그런데 실제 커넥션은 autoCommit=true이라면? 각 쿼리가 즉시 커밋된다.
롤백이 불가능해진다. 에러 없이 조용히 깨지기 때문에 더 위험하다.

 

이게 왜 기본값이 아닌가

이렇게 좋은데 왜 기본값이 아닐까?

provider_disables_autocommit=true는 "풀이 이미 autoCommit=false로 줄 테니 확인하지 마"라는 약속이다.
Hibernate는 이 약속이 진짜인지 검증할 방법이 없다.
JDBC 스펙 자체가 커넥션 기본값을 autoCommit=true로 정하고 있고, 이 약속을 어기면 트랜잭션이 조용히 깨진다.
프레임워크는 안전한 쪽을 기본값으로 가져간다.

 

번외 - setAutoCommit의 숨은 비용

setAutoCommit(false)는 단순한 자바 메서드 호출이 아니다.
MariaDB 드라이버 기준으로 내부적으로 SET autocommit=0이라는 SQL을 DB에 전송한다.
이는 네트워크 라운드트립을 유발한다. (건당 1~3ms)

트랜잭션 하나에 setAutoCommit(false) + setAutoCommit(true) = 라운드트립 2회.
트래픽이 작으면 체감하기 어렵지만, 큰 환경에서는 얘기가 달라진다.

참고한 기술블로그 기준으로 다음과 같은 결과를 얻었다고 한다.

총 응답시간 6ms 중 setAutoCommit에만 4ms 소요 (66%)

 

provider_disables_autocommit=true 적용 후 쿠폰 전체 API 평균 43% 성능 향상을 달성했다.
쿼리가 가볍고 빠른 API일수록 setAutoCommit의 상대적 비중이 커지기 때문이다.

 

Reference

  • Hibernate Jira HHH-11542
  • Vlad Mihalcea - Why you should always use hibernate.connection.provider_disables_autocommit
  • 카카오페이 - JPA Transactional 잘 알고 쓰고 계신가요?
  • 후덥 - Hibernate Auto Commit 튜닝
  • Spring Boot Issue #9261
반응형
저작자표시 (새창열림)

'프로그래밍' 카테고리의 다른 글

MySQL이 SEQUENCE를 지원하지 않는 이유  (0) 2026.04.12
'프로그래밍' 카테고리의 다른 글
  • MySQL이 SEQUENCE를 지원하지 않는 이유
Giwonnnnnnn
Giwonnnnnnn
개발, 일상 등 나에 대한 모든 것을 기록합니다.
    250x250
  • Giwonnnnnnn
    기록하는 곳
    Giwonnnnnnn
  • 전체
    오늘
    어제
    • 분류 전체보기 (89)
      • 프로그래밍 (35)
        • 객체지향 설계 (7)
        • git (1)
        • Network (2)
        • 후기 (2)
        • 기타 (10)
        • 발생한 에러들 (11)
      • Language (18)
        • Java (0)
        • Python (2)
        • JavaScript (13)
        • TIL (3)
      • 알고리즘 (11)
      • BackEnd (1)
        • 데이터베이스 (1)
        • Nest.js (4)
        • Node.js (2)
      • 자격증 공부 (1)
        • 정보처리기사 (0)
      • 영어공부 (3)
        • 문법 기초 (3)
      • Cloud (3)
        • GCP (3)
      • 일상 (4)
        • 리뷰 (1)
        • 맛집 탐방 (1)
        • 일상 (2)
        • 일기장 (0)
  • 블로그 메뉴

    • 홈
    • 방명록
  • 링크

    • Github
    • LinkedIn
    • Instagram
  • 공지사항

  • 인기 글

  • 태그

    Object
    JavaScript
    오브젝트
    프로그래머스
    파이썬
    java
    자바
    알고리즘
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Giwonnnnnnn
@Transactional - 스냅샷은 첫 쿼리에 만드는데, 커넥션은 왜 미리 잡을까
상단으로

티스토리툴바