SQL

[DB] 트랜잭션

코딩로봇 2025. 5. 1. 13:58

금융 IT에서 트랜잭션은 시스템의 신뢰성과 데이터 무결성을 지키는 핵심이다.

은행 송금이나 결제 시스템 같은 비즈니스 로직에서 트랜잭션이 없으면 데이터 꼬임은 물론이고 사용자 신뢰도 무너진다. 이 글은 트랜잭션의 기본 개념, ACID 속성, 상태, 스프링에서의 처리 방식,

그리고 실제 금융 시나리오를 다루며, 최대한 실무 개발자 입장에서 자연스럽게 풀어볼려고 한다.


트랜잭션이란?

  • 데이터베이스에서 하나의 논리적 작업 단위다.
  • 예를 들어, 계좌 이체는 출금과 입금 두 작업으로 나뉘는데, 둘 다 성공하거나 둘 다 실패해야 한다. 하나만 성공하면 데이터가 엉망이 되니까.

다른 예시로는 , 아래와 같다.

  • 💸 계좌 이체: 송금자의 계좌에서 돈이 빠지고, 수취인의 계좌에 돈이 들어가야 한다. 중간에 하나라도 실패하면 전체 작업을 취소해야 한다.
  • ✈️ 비행기여행 예약: 비행기 티켓, 숙소, 비자 발급이 모두 완료되어야 여행이 가능하다. 하나라도 실패하면 나머지도 취소된다.

금융 IT에서는 이런 복잡한 작업을 안전하게 처리하기 위해 트랜잭션이 필수다.

 

 

 

ACID: 트랜잭션의 4가지 필수 속성

  • 트랜잭션은 ACID라는 4가지 속성을 통해 데이터의 신뢰성을 보장한다.
Atomicity (원자성)  모든 작업이 성공하거나, 실패 시 아무것도 반영되지 않는다. 예: 이체 중 입금이 실패하면 출금도 취소.
Consistency (일관성) 트랜잭션 후에도 데이터베이스는 항상 일관된 상태를 유지. 예: 이체 후 계좌 잔액 합계는 변함없음.
Isolation (격리성) 트랜잭션은 서로 독립적으로 실행. 예: 한 사용자의 이체가 다른 사용자의 잔액 조회에 영향 주지 않음.
Durability (지속성) 성공한 트랜잭션은 영구 저장. 예: 시스템 장애가 나도 이체 기록은 남는다

 

 

트랜잭션 상태: 어떤 흐름으로 진행되나?

 

 

  • 활동 상태 (Active) : 트랜잭션이 시작된 상태. 예: 이체 요청 처리 중.
  • 부분 완료 상태 (Partially Committed) : 모든 작업은 끝났지만 커밋은 안 된 상태. 예: 출금, 입금 완료 후 커밋 직전.
  • 완료 상태 (Committed) : 트랜잭션이 커밋되어 데이터베이스에 영구 반영. 예: 이체 기록이 저장됨.
  • 실패 상태 (Failed) : 오류로 진행 불가 상태. 예: 수취인 계좌가 없어 이체 실패.
  • 철회 상태 (Aborted) : 롤백으로 이전 상태로 복구. 예: 출금 기록이 취소됨.

 

 

스프링에서의 트랜잭션 관리

  • 프링은 트랜잭션 처리를 간편하게 해주는 강력한 도구다.
  • 특히 금융 IT에서 @Transactional은 트랜잭션 동작을 제어하는 핵심이다.

♐ 스프링의 롤백 기본 동작

스프링은 예외 발생 시 롤백 여부를 아래처럼 결정한다.

Unchecked Exception (RuntimeException, Error) 기본적으로 롤백. 예: NullPointerException 발생 시 롤백.
Checked Exception (Exception 상속, RuntimeException 제외) 기본적으로 롤백 안 함. 명시적 설정 필요.
rollbackFor 설정 특정 Checked Exception에 대해 롤백 지정 가능.
noRollbackFor 설정 특정 예외에 대해 롤백 제외 가능.

 

♐ 트랜잭션 내부 호출 문제

  • 스프링에서 트랜잭션은 프록시 패턴으로 동작한다. @Transactional이 붙은 메서드는 프록시 객체를 통해 트랜잭션을 시작하고 종료한다.
  • 하지만 같은 클래스 안에서 메서드를 호출하면 프록시를 안 거쳐서 트랜잭션이 적용되지 않는다.
public class MyService {

    private final OrderRepository orderRepository;

    public void save() {
        orderRepository.save(new Order("내부 아이템", 10));
        innerSave();

    }

    @Transactional
    public void innerSave() {
        orderRepository.save(new Order("외부 아이템", 10));
        throw new RuntimeException("내부 예외 발생");
    }
}

 

자세히 보기

https://computerreport.tistory.com/132

 

[Spring] 트랜잭션 내부 호출 문제

https://computerreport.tistory.com/manage/newpost# 티스토리좀 아는 블로거들의 유용한 이야기, 티스토리. 블로그, 포트폴리오, 웹사이트까지 티스토리에서 나를 표현해 보세요.www.tistory.com 해당 글에서 트

computerreport.tistory.com

 

트랜잭션 전파 속성 (Propagation)

  • 전파 속성은 트랜잭션이 다른 트랜잭션과 어떻게 상호작용할지를 정의한다.
  • 금융 시스템에서 주문, 결제, 알림 같은 복잡한 로직을 처리할 때 필수다.
REQUIRED 새 트랜잭션 생성 기존 트랜잭션 참여 기본값. 결제, 주문 처리.
REQUIRES_NEW 새 트랜잭션 생성 기존 트랜잭션 중단, 새 트랜잭션 생성 독립적 결제 처리.
SUPPORTS 트랜잭션 없이 진행 기존 트랜잭션 참여 조회 로직.
NOT_SUPPORTED 트랜잭션 없이 진행 기존 트랜잭션 중단 로그 저장.
MANDATORY 예외 발생 기존 트랜잭션 참여 트랜잭션 필수 로직.
NEVER 트랜잭션 없이 진행 예외 발생 외부 시스템 호출.
NESTED 새 트랜잭션 생성 중첩 트랜잭션 생성 부분 롤백 필요한 경우.

 

  • 예를 들어, 결제는 REQUIRES_NEW로 독립 트랜잭션을 만들고,
  • 알림은 NOT_SUPPORTED로 트랜잭션 없이 실행할 수 있다.

 

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

  • 격리 수준은 트랜잭션 간 데이터 접근을 얼마나 엄격히 제어할지 결정한다.
  • 금융 시스템에서는 데이터 정합성을 위해 적절한 격리 수준을 선택해야 한다. (MySQL 기준)
격리 수준 설명 문제점
READ UNCOMMITTED 커밋 안 된 데이터 읽기 가능. Dirty Read: 잘못된 데이터 읽기.
READ COMMITTED 커밋된 데이터만 읽기 가능. Non-Repeatable Read: 동일 쿼리 결과 불일치.
REPEATABLE READ (MySQL 기본값) 동일 트랜잭션 내 동일 결과 보장. Phantom Read: 추가 데이터로 불일치.
SERIALIZABLE 데이터에 락을 걸어 완전 격리. 성능 저하.

 

💡 금융 시스템에서는 REPEATABLE READ가 많이 쓰인다. 계좌 잔액 조회 시 동일 트랜잭션에서 일관된 결과를 보장해야 하기 때문이다. 하지만 중요한 결제 로직에서는 SERIALIZABLE을 고려할 수 있다.

 

 

정리

  • 트랜잭션: 데이터베이스 작업의 최소 단위. 금융 시스템에선 이게 없으면 데이터 엉망
  • 예: 계좌 이체는 출금+입금이 한 묶음.
  • ACID: 트랜잭션의 4대 원칙. 원자성(전부 되거나 전부 취소), 일관성(데이터 항상 깔끔), 격리성(다른 작업과 충돌 X), 지속성(장애 나도 기록 남음).
  • 상태 흐름: 시작(Active) → 작업 끝, 커밋 전(Partially Committed) → 성공(Committed) or 에러(Failed) → 롤백(Aborted).
  • 스프링에서 트랜잭션 관리:
    • @Transactional로 쉽게 제어. 기본적으론 RuntimeException에서 롤백.
    • 내부 호출 함정: 같은 클래스 안 메서드 호출은 트랜잭션 안 걸림. 클래스 분리하거나 self-injection 써야 함.
    • 전파 속성: REQUIRED(기본, 같이 쓰기), REQUIRES_NEW(따로 트랜잭션), SUPPORTS(트랜잭션 없이도 OK), NOT_SUPPORTED(트랜잭션 끊기), MANDATORY(트랜잭션 필수), NEVER(트랜잭션 절대 X), NESTED(부분 롤백 가능).
    • 격리 수준: REPEATABLE READ(MySQL 기본, 잔액 조회에 딱), SERIALIZABLE(결제처럼 민감한 로직에).