← 개인 공부 · Backend
STUDY NOTE · 개념 정리

트랜잭션 개념 정리: ACID, 격리 수준, 스프링 @Transactional 전파와 롤백 규칙

Backend
트랜잭션은 더 이상 쪼갤 수 없는 작업 단위다. 그 안의 작업은 전부 성공하거나(커밋) 전부 취소된다(롤백). 스프링의 @Transactional은 이 경계를 프록시로 자동 처리한다.

1. 왜 필요한가

계좌 이체는 "A 계좌에서 출금"과 "B 계좌에 입금" 두 작업이다. 출금만 되고 입금 전에 서버가 죽으면 돈이 사라진다. 두 작업을 하나로 묶어 둘 다 되거나 둘 다 안 되게 하는 것이 트랜잭션이다.

2. ACID

성질의미보장 수단 (예)
Atomicity (원자성)전부 성공하거나 전부 실패Undo 로그로 롤백
Consistency (일관성)트랜잭션 전후로 제약 조건이 지켜짐PK, FK, CHECK 제약, 애플리케이션 규칙
Isolation (격리성)동시에 실행되는 트랜잭션이 서로 간섭하지 않음락, MVCC, 격리 수준
Durability (지속성)커밋된 결과는 장애가 나도 유지Redo 로그, 디스크 기록

3. 동시성 문제

현상설명
Dirty Read다른 트랜잭션이 아직 커밋하지 않은 값을 읽음 (나중에 롤백되면 없는 값을 읽은 셈)
Non-Repeatable Read같은 행을 두 번 읽었는데 그 사이 다른 트랜잭션의 수정·커밋으로 값이 달라짐
Phantom Read같은 조건으로 두 번 조회했는데 그 사이 추가·삭제된 행 때문에 행 수가 달라짐

4. 격리 수준 (Isolation Level)

격리 수준Dirty ReadNon-Repeatable ReadPhantom Read
READ UNCOMMITTED발생발생발생
READ COMMITTED방지발생발생
REPEATABLE READ방지방지발생 가능
SERIALIZABLE방지방지방지
  1. 아래로 갈수록 안전하지만 동시 처리 성능은 떨어진다.
  2. 기본값: MySQL/MariaDB(InnoDB)는 REPEATABLE READ, Oracle·PostgreSQL·SQL Server는 READ COMMITTED.
  3. InnoDB는 MVCC(스냅샷)로 일반 SELECT에서는 팬텀도 대부분 보이지 않게 하고, 잠금 읽기에서는 갭 락으로 막는다.

5. 스프링 @Transactional의 동작 원리: 프록시

스프링은 @Transactional이 붙은 빈 대신 프록시 객체를 주입한다. 프록시는 메서드 호출을 가로채서 다음 일을 한다.

  1. 트랜잭션 시작 (커넥션 획득, auto-commit 끔)
  2. 실제 객체의 메서드 호출
  3. 정상 종료면 커밋, 롤백 대상 예외면 롤백
  4. 커넥션 반환

이 구조에서 나오는 규칙이 있다. 트랜잭션은 프록시를 통해 호출될 때만 적용된다.

상황적용 여부이유
다른 빈에서 호출적용프록시를 거침
같은 클래스 내부에서 this.method() 호출미적용프록시를 거치지 않음 (self-invocation)
private 메서드미적용프록시가 가로챌 수 없음

6. 롤백 규칙

예외 종류기본 동작
RuntimeException (Unchecked)롤백
Error롤백
Exception (Checked, 예: IOException)커밋

체크 예외에도 롤백하려면 @Transactional(rollbackFor = Exception.class)를 쓴다. 그리고 메서드 안에서 예외를 catch하고 다시 던지지 않으면, 프록시는 정상 종료로 보고 커밋한다.

7. 전파 속성 (Propagation)

이미 트랜잭션이 진행 중일 때 새 @Transactional 메서드를 호출하면 어떻게 할지 정한다.

전파 속성기존 트랜잭션이 있으면없으면
REQUIRED (기본값)참여새로 시작
REQUIRES_NEW기존을 보류하고 새로 시작새로 시작
SUPPORTS참여트랜잭션 없이 실행
MANDATORY참여예외 발생
NOT_SUPPORTED보류하고 트랜잭션 없이 실행트랜잭션 없이 실행
NEVER예외 발생트랜잭션 없이 실행
NESTED세이브포인트를 만들고 중첩 실행새로 시작

REQUIRED로 참여한 안쪽 메서드에서 런타임 예외가 나면, 바깥에서 그 예외를 잡더라도 공유 중인 트랜잭션 전체가 rollback-only로 표시된다. 바깥이 커밋을 시도하면 UnexpectedRollbackException이 나며 전체가 롤백된다.

8. 그 밖의 속성

속성의미
readOnly = true읽기 전용 힌트. JPA는 변경 감지를 생략해 가벼워지고, 읽기 전용 DB로 라우팅하는 기준이 되기도 함
isolation이 트랜잭션의 격리 수준 지정 (기본은 DB 설정)
timeout제한 시간 초과 시 롤백

9. 실제로 어떻게 적용되나

  1. 주문 생성: 주문 저장, 재고 차감, 포인트 사용을 한 서비스 메서드에 @Transactional로 묶는다. 재고 부족 예외(RuntimeException)가 나면 앞서 저장한 주문까지 롤백된다(원자성).
  2. 댓글 저장과 포인트 지급 분리: 포인트 지급이 실패해도 댓글은 저장돼야 한다면, 포인트 지급을 REQUIRED로 두고 예외를 잡는 것만으로는 안 된다(rollback-only 표시 때문). REQUIRES_NEW로 별도 트랜잭션을 열거나, 커밋 후 이벤트(@TransactionalEventListener(phase = AFTER_COMMIT))로 분리한다.
  3. 같은 클래스 안에서 호출: 반복문 안에서 건별로 트랜잭션을 걸려고 같은 클래스의 @Transactional 메서드를 호출하면 적용되지 않는다. 그 메서드를 다른 빈으로 분리해야 한다.
  4. 외부 API 호출 위치: 트랜잭션 안에서 느린 외부 API를 호출하면 그동안 DB 커넥션을 붙잡아 커넥션 풀이 고갈될 수 있다. 외부 호출은 트랜잭션 밖으로 뺀다.

10. 면접 질문으로 정리

  1. Q. 트랜잭션과 ACID란? 트랜잭션은 전부 성공하거나 전부 실패해야 하는 작업 단위이며, 원자성·일관성·격리성·지속성을 보장해야 한다.
  2. Q. 격리 수준과 각 수준에서 발생하는 문제는? READ UNCOMMITTED는 Dirty Read, READ COMMITTED는 Non-Repeatable Read, REPEATABLE READ는 Phantom Read가 발생할 수 있고, SERIALIZABLE은 모두 방지하지만 동시성이 가장 낮다. MySQL InnoDB의 기본은 REPEATABLE READ다.
  3. Q. @Transactional은 어떻게 동작하나? 스프링이 빈을 프록시로 감싸 메서드 호출 전후에 트랜잭션 시작·커밋·롤백을 처리한다. 그래서 같은 클래스 내부 호출이나 private 메서드에는 적용되지 않는다.
  4. Q. 어떤 예외에서 롤백되나? 기본적으로 RuntimeException과 Error에서 롤백되고 체크 예외는 커밋된다. rollbackFor로 변경할 수 있다.
  5. Q. REQUIRED와 REQUIRES_NEW의 차이는? REQUIRED는 기존 트랜잭션에 참여해 하나로 커밋·롤백되고, REQUIRES_NEW는 기존 트랜잭션을 보류하고 독립된 새 트랜잭션으로 커밋·롤백된다.
Gunmo Lee