Transaction

트랜잭션 ACID 특성

트랜잭션(Transaction)은 데이터베이스에서 여러 작업을 하나의 논리적인 단위로 묶어 실행하는 개념. 트랜잭션이 ACID 특성을 가지는 이유는 데이터의 무결성과 일관성을 보장하기 위함.

Atomicity (원자성)

  • 트랜잭션 내의 작업은 모두 성공하거나 모두 실패해야 한다.
  • 중간에 실패할 경우, 모든 변경 사항을 롤백(Rollback)하여 데이터베이스가 일관된 상태를 유지해야 합니다.
  • 예시: 은행 계좌 이체에서 A 계좌에서 100원을 빼고 B 계좌에 100원을 넣는 과정에서 하나라도 실패하면 전체가 롤백되어야 한다.

Consistency (일관성)

  • 트랜잭션이 실행되기 전과 후에 데이터베이스는 항상 일관된 상태를 유지해야 한다.
  • 트랜잭션이 완료되면 모든 데이터는 정의된 무결성 제약 조건을 만족해야 한다.
  • 예시: 한 학생이 수강 신청을 했을 때, 강의의 정원이 줄어들어야 하며, 반대로 취소하면 정원이 증가해야 한다.

Isolation (고립성)

  • 여러 트랜잭션이 동시에 실행될 때 서로 간섭하지 않도록 해야 한다.
  • 격리 수준(Isolation Level)을 조절하여 데이터 정합성을 보장할 수 있다.
  • 예시: 두 개의 트랜잭션이 동일한 제품의 재고를 줄이는 경우, 한 트랜잭션이 완료되기 전에 다른 트랜잭션이 영향을 주면 안 된다.

Durability (지속성)

  • 트랜잭션이 성공적으로 완료되면, 시스템 장애가 발생하더라도 데이터는 영구적으로 저장되어야 한다.
  • 일반적으로 WAL(Write-Ahead Logging)과 같은 기법을 사용하여 장애 발생 시에도 데이터를 복구할 수 있어야 한다.
  • 예시: 은행 계좌 잔액이 갱신된 후 시스템이 다운되더라도 변경된 데이터는 저장되어 있어야 합니다.

ACID를 보장하기 위한 주요 기술

  • Atomicity & Durability → 로그(Write-Ahead Logging), 체크포인트(Checkpoint)
  • Consistency → 트랜잭션 제약 조건(Constraints), 데이터 무결성 보장
  • Isolation → 격리 수준 설정 (Read Committed, Repeatable Read, Serializable 등)

트랜잭션 전파(Propagation) 옵션

트랜잭션 전파(Propagation)는 하나의 트랜잭션이 실행 중일 때 새로운 트랜잭션을 어떻게 처리할지 결정하는 옵션.

옵션 설명
REQUIRED (기본값) 기존 트랜잭션이 있으면 참여하고, 없으면 새 트랜잭션 생성
REQUIRES_NEW 항상 새로운 트랜잭션을 생성, 기존 트랜잭션은 일시 정지
NESTED 부모 트랜잭션 안에서 별도의 트랜잭션 실행 (Rollback 독립적 가능)
MANDATORY 반드시 기존 트랜잭션이 있어야 실행 (없으면 예외 발생)
SUPPORTS 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행
NOT_SUPPORTED 트랜잭션 없이 실행, 기존 트랜잭션은 일시 정지
NEVER 트랜잭션이 있으면 예외 발생

트랜잭션 격리 수준(Isolation Level)

  • 여러 트랜잭션이 동시에 실행될 때 데이터의 일관성을 보장하기 위해 데이터베이스가 적용하는 규칙.
  • SQL 표준에서는 총 4가지 격리 수준을 정의하고 있으며, 각 수준은 발생할 수 있는 문제를 방지하는 정도에 따라 나뉜다.
  • 대부분의 애플리케이션에서는 Dirty Read(더티 리드)만 방지하면 충분한 경우가 많음.
격리 수준 (Isolation Level) Dirty Read (더티 리드) Non-Repeatable Read (반복 불가능한 읽기) Phantom Read (팬텀 리드) 특징
Read Uncommitted (읽기 미완료 허용) 발생 가능 발생 가능 발생 가능 가장 낮은 격리 수준, 미확정 데이터 읽기 가능
Read Committed (읽기 완료 허용) 방지 발생 가능 발생 가능 커밋된 데이터만 읽음
Repeatable Read (반복 가능 읽기) 방지 방지 발생 가능 같은 트랜잭션 내 동일한 조회 보장
Serializable (직렬화) 방지 방지 방지 가장 엄격한 격리 수준, 동시성 낮음

DBMS별 트랜잭션 격리 수준

DBMS 기본 격리 수준 지원하는 격리 수준
MySQL (InnoDB) Repeatable Read Read Uncommitted, Read Committed, Repeatable Read, Serializable
Oracle Read Committed Read Committed, Serializable, Read-Only
PostgreSQL Read Committed Read Committed, Repeatable Read, Serializable
MongoDB Read Committed 수준 (일관성 모델이 다름) No explicit isolation levels (document-level isolation)
  • MySQL InnoDB 스토리지 엔진은 Repeatable Read가 기본값이며, 이를 유지하면서 Gap Lock + Next-Key Lock을 통해 팬텀 리드까지 방지 가능. 일반적인 Repeatable Read보다 강력하여 사실상 Serializable에 가까운 수준을 제공.
  • PostgreSQL은 MVCC 기반으로 구현되어 Read Committed에서도 높은 일관성을 유지함.
  • MongoDB는 RDBMS와 다른 격리 수준을 가짐. 기본적으로 Read Committed 수준의 일관성을 제공하지만, 트랜잭션을 지원하는 경우 Snapshot Isolation 수준을 제공 가능.

스프링의 트랜잭션 처리

애플리케이션 레벨

  • AOP 기반 프록시 생성
    • 스프링은 @Transactional이 적용된 클래스나 메서드를 감싸기 위해 AOP(Aspect-Oriented Programming)를 사용하여 프록시 객체를 생성한다.
    • 이 프록시는 실제 비즈니스 로직을 호출하기 전후에 트랜잭션 시작, 커밋, 롤백 등의 부가기능을 수행한다.
  • 트랜잭션 시작
    • @Transactional 메서드가 호출되면, 스프링의 트랜잭션 어드바이스가 개입하여 트랜잭션 관리자(예: PlatformTransactionManager)를 통해 트랜잭션을 시작한다.
    • 이 과정에서 트랜잭션 전파(Propagation), 격리 수준(Isolation), 타임아웃(timeout) 등의 속성에 따라 기존 트랜잭션에 참여하거나 새로운 트랜잭션을 생성할 수 있다.
  • 메서드 실행 및 예외 처리
    • 트랜잭션이 시작된 후, 실제 비즈니스 로직이 수행된다.
    • 메서드 실행 도중 예외가 발생하면, 스프링은 기본적으로 RuntimeException이나 Error를 감지하여 트랜잭션을 롤백한다(필요시 rollbackFor 등의 옵션으로 체크 예외에 대해서도 롤백하도록 설정할 수 있음).
  • 커밋 또는 롤백
    • 메서드가 정상적으로 종료되면, 트랜잭션 관리자에 의해 트랜잭션이 커밋되어 모든 변경 사항이 확정. 반대로, 예외가 발생하면 롤백하여 변경 사항이 모두 취소.

데이터베이스 레벨

  • 커넥션 획득 및 Auto-Commit 해제
    • 트랜잭션 시작 시, 스프링은 데이터 소스로부터 커넥션을 획득하고 해당 커넥션의 autoCommit 모드를 false로 설정한다.
    • 이렇게 함으로써, 여러 SQL 명령이 하나의 트랜잭션 내에서 실행될 수 있도록 보장한다.
  • SQL 실행 및 트랜잭션 경계 내 작업
    • 트랜잭션이 활성화된 동안 수행되는 모든 SQL 명령(삽입, 수정, 삭제 등)은 동일한 커넥션과 트랜잭션 컨텍스트를 공유한다.
    • 데이터 변경 작업은 커밋이 이루어질 때까지 DB에 확정되지 않고, 필요한 경우 다른 트랜잭션과의 격리 수준에 따라 잠금(lock)이나 동시성 제어가 적용된다.
  • 커밋 또는 롤백
    • 커밋: 애플리케이션에서 메서드가 정상적으로 완료되면, 트랜잭션 관리자가 커넥션에 커밋 명령을 내려 모든 변경 사항이 영구적으로 DB에 반영.
    • 롤백: 반대로, 메서드 실행 중 예외가 발생하거나 트랜잭션 관리자가 롤백을 결정하면, DB는 롤백 명령을 받아 모든 변경 사항을 취소.
  • 리소스 정리
    • 트랜잭션이 종료되면, 커넥션은 다시 풀(Pool)로 반환되며, 스프링은 TransactionSynchronizationManager 등을 통해 트랜잭션과 관련된 리소스(예: 커넥션)를 정리한다.

@Transactional 어노테이션 사용시 주의사항

  • 메서드가 public이어야 한다
    • @Transactional은 스프링 AOP 기반으로 동작하기 때문에 프록시 객체가 생성된다.
    • 내부에서 private 또는 protected 메서드를 호출하면 트랜잭션이 적용되지 않는다.
  • 동일 클래스 내부의 메서드 호출은 트랜잭션이 적용되지 않음
    • 스프링의 기본 트랜잭션 관리 방식은 프록시 기반 AOP이기 때문에, 같은 클래스 내에서 @Transactional이 붙은 메서드를 호출하면 트랜잭션이 적용되지 않는다.
  • @Transactional이 인터페이스에서는 동작하지 않을 수도 있음
    • JDK 동적 프록시(Proxy.newProxyInstance)는 인터페이스 기반으로 생성된다.
    • 인터페이스가 존재하면 프록시가 인터페이스를 기준으로 생성되므로, 구현체의 @Transactional이 적용되지 않을 수도 있다.
    • 해결 방법: proxyTargetClass = true로 설정하여 CGLIB(클래스 기반 프록시) 을 사용하도록 설정해야 한다. @EnableTransactionManagement(proxyTargetClass = true)
  • @Transactional(readOnly = true)의 사용
    • @Transactional(readOnly = true)를 사용하면 쓰기 연산이 차단되며, 데이터베이스의 성능이 최적화.
    • 하지만 쓰기 연산을 하면 예외가 발생할 수 있으므로 주의해야 한다.
  • Checked Exception은 트랜잭션 롤백이 되지 않음
    • 스프링의 기본 설정에서는 RuntimeException과 Error가 발생해야 자동 롤백됩니다.
    • Checked Exception이 발생하면 기본적으로 트랜잭션이 커밋됩니다.

Categories:

Updated:

Comments