Spring 트랜잭션 전파와 격리 수준

최정윤·2026년 7월 21일

Spring

목록 보기
22/38

트랜잭션 전파내부 작업을 기존 트랜잭션과 함께 처리할지 분리할지 결정하고, 격리 수준동시에 실행되는 트랙잭션끼리 데이터를 어디까지 볼 수 있는지 결정한다.


트랜잭션 전파가 필요한 이유

@Transactional 메서드 안에서 다른 @Transactional 메서드를 호출하면, 두 작업을 같은 트랜잭션으로 묶을지 별도로 실행할지 결정해야 한다. 이를 트랜잭션 전파라고 한다.

옵션동작주요 용도
REQUIRED기존 트랜잭션이 있으면 참여하고, 없으면 새로 생성일반적인 비즈니스 로직
REQUIRES_NEW기존 트랜잭션을 중단하고 별도의 트랜잭션 생성본 작업이 실패해도 남겨야 하는 로그·이력

REQUIRED는 기본값이므로 대부분 별도로 작성하지 않는다. REQUIRES_NEW는 메인 기능의 성공 여부와 관계없이 반드시 기록할 데이터에 사용한다.

REQUIRED와 REQUIRED_NEW의 트랜잭션 참여 방식
기존 트랜잭션에 참여하는 REQUIRED와 별도의 물리적 트랜잭션을 생성하는 REQUIRES_NEW의 차이를 비교한 그림이다.


주문이 취소되어도 시도 이력은 남겨야 하는 이유

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final OrderLogService orderLogService;
    private final PointService poinService;
    
    
    @Transactional       // 트랜잭션 A
    public void order(OrderRequest request) {
    
        orderLogService.saveLog(request);
        orderRepository.save(new Order(request));
        pointService.usePoint(request);        // 예외 발생
    }
}
@Service
@RequiredArgsContructor
public class OrderLogService {

    private final OrderLogRepository logRepository;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(OrderRequest request) { 
         logRepository.save(new OrderLog(request));
      }
   }

실행 흐름은 다음과 같다.

트랜잭션 A 시작
    ↓
saveLog() 호출
    ↓
A 일시 중단
    ↓
트랜잭션 B 시작
    ↓
주문 시도 이력 저장
    ↓
B Commit
    ↓
A 재개
    ↓
포인트 차감 중 예외
    ↓
A Rollback

결과는 다음과 같다.

주문 데이터     → Rollback으로 취소
주문 시도 이력 → 별도 Commit되어 유지

REQUIRED가 한 배를 타는 관계라면, REQUIRES_NEW는 서로 다른 배를 타는 관계다. 부모 트랜잭션이 롤백되어도 이미 커밋된 별도 트랜잭션은 영향을 받지 않는다.

실무 주의사항

첫째, 로그 저장 중 발생한 예외가 외부로 전달되면 주문 트랜잭션까지 실패할 수 있다. 로그 실패가 본 기능을 중단시키면 안 되는 정책이라면 예외를 별도로 처리해야 한다.

try {
    orderLogService.saveLog(request);
} catch (RuntimeException e) {
    // 로그 실패 처리
}

둘째, 같은 클래스 내부에서 REQUIRES_NEW 메서드를 직접 호출하면 Spring 프록시를 거치지 않아 새 트랜잭션이 적용되지 않을 수 있다. 전파 옵션이 필요한 기능은 별도 Service로 분리하는 편이 안전하다.


트랜잭션 격리 수준이 필요한 이유

여러 트랜잭션이 동시에 같은 데이터를 조회하고 수정하면 결과가 달라질 수 있다. 격리 수준한 트랜잭션이 다른 트랜잭션의 변경 내용을 어디까지 볼 수 있는지 결정한다.

격리 약함                                      격리 강함
동시 처리 성능 높음                            데이터 일관성 높음

READ          READ          REPEATABLE        SERIALIZABLE
UNCOMMITTED   COMMITTED     READ
───────────────────────────────────────────────►

세 가지 이상 현상

현상의미주요 원인
Dirty Read다른 트랜잭션이 아직 커밋하지 않은 값을 읽음미커밋 데이터 노출
Non-Repeatable Read같은 행을 다시 읽었는데 값이 달라짐기존 행의 UPDATE
Phantom Read같은 범위를 다시 조회했는데 행 개수가 달라짐INSERT 또는 DELETE
Non-Repeatable Read = 기존 행의 값이 달라지는 문제
Phantom Read        = 조회 결과의 행 개수가 달라지는 문제
격리 수준Dirty ReadNon-Repeatable ReadPhantom Read
READ UNCOMMITTED발생 가능발생 가능발생 가능
READ COMMITTED방지발생 가능발생 가능
REPEATABLE READ방지방지발생 가능
SERIALIZABLE방지방지방지

MySQL InnoDB의 기본 격리 수준은 REPEATABLE READ이며, 일반적인 Spring 설정에서는 별도 변경 없이 DB 기본값을 따른다.


실무에서의 선택 기준

일반적인 비즈니스 로직은 다음 설정으로 충분하다.

@Transactional
public void process() {
    // 비즈니스 로직
}

이는 다음 기본 설정을 사용한다.

Propagation = REQUIRED
Isolation   = DEFAULT

전파와 격리 수준은 특별한 이유 없이 변경하지 않는다.

일반 비즈니스 로직
→ REQUIRED

본 기능이 실패해도 남아야 하는 이력
→ REQUIRES_NEW 검토

격리 수준
→ DB 기본값 우선

격리 수준높이면 이상 현상은 줄어들지만 Lock과 대기가 증가해 동시 처리 성능이 낮아질 수 있다. 따라서 문제가 확인되지 않은 상태에서 무조건 높은 수준을 선택하는 것은 적절하지 않다.


마무리

트랜잭션 전파와 격리 수준은 해결하는 문제가 다르다.

전파
→ 여러 메서드의 작업을 같은 트랜잭션으로 묶을지 결정

격리 수준
→ 동시에 실행되는 트랜잭션이 서로의 데이터를 어디까지 볼지 결정

핵심은 REQUIRED, REQUIRES_NEW, 세 가지 이상 현상을 구분하는 것이다. 일반적인 상황에서는 기본값을 사용하고, 본 기능과 운명을 같이하면 안 되는 기록이 있을 때만 REQUIRES_NEW를 검토한다.

profile
콩떡

0개의 댓글