[DataBase] 트랜잭션 알아보기

klmin·2024년 11월 3일

트랜잭션 이란?

애플리케이션에서는 여러 사용자가 동시에 데이터를 읽고 쓰는 일이 매우 흔하다.

이 과정에서 데이터의 일관성을 유지하고 오류나 충돌을 방지하기 위한 중요한 개념이 바로 트랜잭션(Transaction)이다.

트랜잭션을 이해하는 것은 데이터베이스를 효율적으로 관리하고 애플리케이션의 안정성을 보장하는 데 필수적이다.

트랜잭션(Transaction)은 데이터베이스에서 수행하는 하나의 논리적인 작업 단위이다.
예를 들어, 은행 계좌에서 송금하는 과정은 출금과 입금이라는 두 개의 작업으로 이루어지지만 하나의 트랜잭션으로 묶여야 데이터의 일관성이 유지된다.
트랜잭션은 모든 작업이 성공하거나 실패할 경우 이전 상태로 돌아가야 하는 원자성을 보장한다.

commit : 트랜잭션 내의 모든 작업이 성공적으로 완료되었음을 데이터베이스에 알리고 변경된 내용을 영구적으로 반영하는 명령.

rollback : 트랜잭션 내의 작업 중 오류가 발생하거나 사용자가 취소를 요청한 경우 트랜잭션 내에서 수행된 모든 작업을 취소하고 작업 시작 전 상태로 되돌리는 명령.

예시)

계좌 이체: 한 계좌에서 출금하고 다른 계좌로 입금하는 두 작업이 함께 성공하거나 실패해야 함.
상품 주문: 주문 생성, 재고 감소, 결제 승인 등의 여러 작업이 하나의 작업 단위로 묶여야 함.

스프링에서 등록 기능에 5개의 테이블에 등록을 하게 되어있다면 5개의 테이블에 모두 등록되거나 모두 등록되지 않아야 함.

트랜잭션의 ACID 특성

트랜잭션은 데이터의 일관성을 보장하기 위해 ACID 특성을 준수해야 한다.
ACID는 원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 지속성(Durability)의 약자이다.

1. 원자성 (Atomicity)

  • 트랜잭션의 작업들은 모두 성공하거나 모두 실패해야 한다.
    일부 작업만 성공하고 일부 작업이 실패하는 상황은 없어야 한다.
    하나의 작업이라도 실패하면 전체 작업이 취소되고 데이터베이스는 트랜잭션 시작 전의 상태로 돌아가야 한다.
  • 예시) 계좌 이체 시 출금은 성공했지만 입금이 실패하는 상황이 발생해서는 안된다.
    만약 입금이 실패하면 출금 작업도 취소되어야 한다.

2. 일관성 (Consistency)

  • 트랜잭션이 완료되면 데이터베이스는 항상 일관된 상태를 유지해야 한다.
    트랜잭션 전후로 데이터베이스의 제약 조건이 깨지지 않아야 한다.
  • 예시) 재고 관리 시스템에서 상품의 재고가 음수가 되는 경우는 없도록 해야 한다.

3. 고립성 (Isolation)

  • 각각의 트랜잭션은 독립적으로 실행되어야 한다.
    트랜잭션이 실행 중일 때 다른 트랜잭션이 그 작업을 간섭하지 못하게 해야 한다.
    이를 통해 하나의 트랜잭션이 진행되는 동안 중간 결과가 다른 트랜잭션에 노출되지 않아야 한다.
  • 예시) 동시에 두 사용자가 같은 상품을 구매하는 상황에서 각 트랜잭션이 독립적으로 처리되어야 재고를 동시에 차감하는 일이 발생하지 않는다.
    이렇게 하면 두 사용자가 동시에 재고를 차감하여 재고가 음수로 가는 상황을 방지할 수 있다.

트랜잭션 격리 수준 종류
1. READ UNCOMMITTED (읽기 미완료)
 - 커밋되지 않은 데이터를 읽을 수 있는 가장 낮은 격리 수준
2. READ COMMITTED (읽기 커밋)
 - 커밋된 데이터만 읽을 수 있는 격리 수준. 커밋되지 않은 데이터를 읽지 않는다.
3. REPEATABLE READ (반복 가능 읽기)
 - 트랜잭션이 시작된 시점에 조회한 데이터가 트랜잭션 내에서 변경되지 않도록 보장한다.
4. SERIALIZABLE (직렬화 가능)
 - 가장 높은 격리 수준. 모든 트랜잭션을 순차적으로 실행하여 동시성 문제를 방지한다.

4. 지속성 (Durability)

  • 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 저장되어야 한다.
    시스템 오류나 충돌이 발생하더라도 커밋된 데이터가 사라지지 않아야 한다.
  • 예시) 주문이 성공적으로 완료되었다면시스템 장애가 발생하더라도 주문 정보는 데이터베이스에 남아 있어야 한다.

Spring

Spring 에서는 기본적으로 트랜잭션을 제공하며 @Transactional로 사용가능하다.

전파유형 - 트랜잭션 경계를 어떻게 처리할지 정의

기본 전파유형 : Propagation.REQUIRED

사용법 : @Transactional(propagation = Propagation.REQUIRED.....)

1. REQUIRED (기본값)

  • 트랜잭션이 이미 존재하면 해당 트랜잭션에 참여하고 없으면 새로운 트랜잭션을 시작한다.

    • 기존 트랜잭션이 있는 경우 : 현재 실행 중인 트랜잭션에 참여하여 작업을 수행한다.
    • 기존 트랜잭션이 없는 경우 : 새로운 트랜잭션을 생성하고 해당 트랜잭션 내에서 작업을 수행한다.
  • 용도 : 여러 메서드 호출이 하나의 트랜잭션으로 묶여야 할 때 사용한다.

2. REQUIRES_NEW

  • 항상 새로운 트랜잭션을 시작한다. 현재 트랜잭션과 별개로 새 트랜잭션이 시작된다.

    • 기존 트랜잭션이 있는 경우 : 기존 트랜잭션을 일시 중단하고 새로운 트랜잭션에서 작업을 수행한다.
    • 기존 트랜잭션이 없는 경우 : 새로운 트랜잭션을 시작하고 작업을 수행한다.
  • 용도 : 독립적으로 처리해야 하는 작업에 사용.

3. NESTED

  • 중첩 트랜잭션을 시작한다. 현재 트랜잭션이 없으면 REQUIRED처럼 새로운 트랜잭션을 시작하고 트랜잭션이 있다면 서브 트랜잭션으로 실행된다.

    • 기존 트랜잭션이 있는 경우 : 현재 트랜잭션에 중첩된 서브 트랜잭션으로 실행된다.
      서브 트랜잭션은 상위 트랜잭션이 롤백될 경우 함께 롤백된다.
    • 기존 트랜잭션이 없는 경우 : 새로운 트랜잭션을 시작하고 작업을 수행한다.
  • 용도 : 상위 트랜잭션에 종속적인 작업이 필요할 때 사용한다.
    상위 트랜잭션 내에서 일부 작업을 부분적으로 롤백해야 하는 경우에 사용. 단계별 작업 처리.

4. MANDATORY

  • 반드시 기존 트랜잭션이 있어야 한다. 트랜잭션이 없으면 예외가 발생한다.

    • 기존 트랜잭션이 있는 경우 : 현재 트랜잭션에 참여하여 작업을 수행한다.
    • 기존 트랜잭션이 없는 경우 : 예외가 발생한다.
  • 용도 : 상위 트랜잭션과 함께 실행되어야 하는 작업에 사용된다.
    호출하는 메서드가 항상 트랜잭션 내에서 실행되어야 할 때 강제하는 용도로 사용.

5. SUPPORTS

  • 트랜잭션이 있으면 그 트랜잭션에서 실행하고 없으면 비트랜잭션 방식으로 실행된다.

    • 기존 트랜잭션이 있는 경우 : 현재 트랜잭션에 참여하여 작업을 수행한다.
    • 기존 트랜잭션이 없는 경우 : 트랜잭션 없이 작업을 수행한다.
  • 용도 : 트랜잭션이 있어도 되고 없어도 무방한 작업에 사용된다.

6. NOT_SUPPORTED

  • 트랜잭션이 있으면 일시 중단하고 트랜잭션 없이 작업을 수행한다.

    • 기존 트랜잭션이 있는 경우 : 현재 트랜잭션을 일시 중단하고 비트랜잭션 방식으로 작업을 수행한다.
    • 기존 트랜잭션이 없는 경우 : 비트랜잭션 방식으로 작업을 수행한다.
  • 용도 : 트랜잭션이 필요 없는 작업에 사용된다.

7. NEVER

  • 트랜잭션 없이만 실행할 수 있다. 트랜잭션이 존재하면 예외를 발생시킨다.

    • 기존 트랜잭션이 있는 경우 : 예외가 발생한다.
    • 기존 트랜잭션이 없는 경우 : 비트랜잭션 방식으로 작업을 수행한다.
  • 용도 : 트랜잭션 없이 실행되어야 하는 작업을 강제하는 용도로 사용.

격리 수준 - 동시에 여러 트랜잭션이 실행될 때 각 트랜잭션이 서로에게 미치는 영향을 어느 정도까지 허용할지 설정하는 옵션

기본 격리 수준 : Isolation.DEFAULT

사용법 : @Transactional(isolation = Isolation.DEFAULT....)

데이터베이스마다 기본 격리 수준이 다를 수 있으며 일반적으로는 READ COMMITTED가 많이 사용된다.

MySQL에서는 기본 격리 수준이 REPEATABLE READ이고 Oracle, Postgres, SQL Server에서는 READ COMMITTED가 기본값이다.

1. DEFAULT(기본값)

  • 데이터베이스의 기본 격리 수준을 사용한다.
  • 용도 : 별도의 설정이 필요하지 않을 때 사용.

2. READ_UNCOMMITTED

  • 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽을 수 있다.
  • 문제점 : 더티 리드(Dirty Read) 발생 가능성이 있다.
  • 용도 : 데이터 일관성보다 성능이 더 중요한 경우에 사용한다.
  • Dirty Read : 트랜잭션의 작업이 완료되지 않았는데도 다른 트랜잭션에서 해당 데이터를 읽는 현상.

3. READ_COMMITTED

  • 커밋된 데이터만 읽을 수 있다. 다른 트랜잭션의 커밋되지 않은 변경 사항은 볼 수 없습다.
  • 문제점 : 반복 불가능한 읽기(Non-repeatable Read)가 발생할 수 있다.
  • 용도 : 대부분의 RDBMS에서 기본 설정으로 사용된다.
  • Non-Repeatable Read : 한 트랜잭션 안에서 똑같은 SELECT 쿼리(read)를 실행했을 때 항상 같은 결과를 가져오지 않는 현상.

4. REPEATABLE_READ

  • 트랜잭션이 시작될 때 조회한 데이터는 트랜잭션이 끝날 때까지 일관성 있게 유지된다. 동일한 데이터를 두 번 조회해도 값이 변경되지 않는다.
  • 문제점 : 팬텀 리드(Phantom Read)가 발생할 수 있다.
  • 용도 : 동일 트랜잭션 내에서 데이터가 변하지 않아야 하는 경우에 적합하다.
  • Phantom Read : 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안 보였다가 하는 현상.

5. SERIALIZABLE

  • 가장 높은 격리 수준으로 모든 트랜잭션을 순차적으로 실행하여 동시성 문제를 방지한다.
  • 문제점 : 성능이 가장 낮아질 수 있다.
  • 용도 : 완벽한 데이터 일관성이 요구되는 경우에만 사용.
profile
웹 개발자

0개의 댓글