1. 트랜잭션

하나의 논리적인 작업 셋 자체가 100% 적용되거나 또는 아무것도 적용되지 않아야 함을 보장해 주는 것.

2. 트랜잭션의 성질

ACID

  • 원자성(Atomicity) - 트랜잭션 내에서 실행한 작업들은 모두 성공하거나 모두 실패해야한다.
  • 일관성(Counsistency) - 일관성 있는 데이터베이스 상태를 유지해야 한다. (무결성 제약 조건 만족)
  • 격리성(Isolation) - 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다.
  • 지속성(Durability) - 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다.

3. 트랜잭션 격리레벨

  • 트랜잭션은 원자성, 일관성, 지속성을 보장한다
  • 문제는 격리성 - 격리성을 완벽히 보장하려면 거의 차례대로 실행해야 하는데 이러면 동시성 처리 성능이 매우 나빠진다.
  • ANSI 표준은 트랜잭션 격리 수준을 4단계로 나누어 정의했다.
  1. READ UNCOMMITED(커밋되지 않은 읽기)
  2. READ COMMITED(커밋된 읽기)
  3. REPEATABLE READ(반복 가능한 읽기)
  4. SERIALIZABLE(직렬화 가능)
격리수준 DIRTY READ NON-REPEATABLE READ PHANTOM READ
READ UNCOMMITTED O O O
READ COMMITTED O O
REPEATABLE READ O
SERIALIABLE
  • DIRTY READ

    • 커밋되지 않은 데이터를 읽을 수 있는 문제
    • 트랜잭션 2가 Dirty read한 데이터를 사용하는데 트랜잭션 1이 롤백되면 심각한 데이터 정합성 문제가 발생할 수 있다.
  • NON-REPEATABLE READ

    • 반복해서 같은 데이터를 읽을 수 없는 상태
    • 트랜잭션 일관성에 위배
  • PHANTOM READ

    • 반복 조회시 결과 집합이 달라지는 것

(참고 1) READ COMMITTED와 REPEATABLE READ의 성능 비교

  • 실제 온라인 서비스 상황에서는 의도적인 경우가 아니라면 NON REPEATABLE READ의 발생 가능성이 거의 없다.
  • 의도적으로 NON REPEATABLE READ를 발생시키는 대표적인 예
    • 하나의 트랜잭션을 열어 그 트랜잭션에서 모든 테이블의 데이터를 SELECT한 후, 그대로 계속 놔두면 Undo 영역이 계속 커져서 시스템 테이블스페이스의 I/O가 유발되는 경우
  • Real MySQL에 제시된 벤치마크 결과에 따르면
  • 1G, 30GB 크기의 테이블에서는 REPEATABLE READ가 2% 정도 높은 성능을 보임
  • 100GB 크기의 테이블에서는 READ COMMITTED가 7% 정도 높은 성능을 보임

(참고 2) JPA를 사용할 때의 트랜잭션 격리레벨

  • Jpa의 영속성 컨텍스트(1차 캐시)를 적절히 활용하면 DB 트랜잭션이 READ COMMITTED 격리 수준이어도 애플리케이션 레벨에서 REPEATABLE READ가 가능하다.
  • 만약 일부 로직에 더 높은 격리 수준이 필요하면 낙관적 락이나 비관적 락을 사용하면 된다.

4. 낙관적 락(Optimistic Locking)과 비관적 락(pessimistic Locking)

  1. 낙관적 락
    • 각 트랜잭션이 같은 레코드를 변경할 가능성은 상당히 희박할 것이라고(낙관적으로) 가정한다.
    • 우선 변경 작업을 수행하고 잠금 충돌이 있었는지 확인해 문제가 있었다면 롤백처리한다.
  2. 비관적 락
    • 현재 변경하고자 하는 레코드를 다른 트랜잭션에서도 변경할 수 있다고(비관적으로) 가정한다.
    • 변경하고자 하는 레코드에 대해 잠금을 획득한 이후 변경작업을 처리한다.
    • 높은 동시성 처리에는 비관적 잠금이 유리하다고 알려져 있으며 InnoDB는 비관적 잠금 방식을 채택하고 있다.

5. 트랜잭션 전파타입 (스프링)

@Transactional의 옵션중 propagation을 통해 설정

진행 중인 트랜잭션 O 진행 중인 트랜잭션 X
REQUIRED (기본값) 해당 트랜잭션 사용 새로운 트랜잭션 생성
MANDATORY 해당 트랜잭션 사용 예외 발생
REQUIRES_NEW 해당 트랜잭션 보류, 새로운 트랜잭션 생성 새로운 트랜잭션 생성
NESTED 중첩 트랜잭션 생성 새로운 트랜잭션 생성
SUPPORTS 해당 트랜잭션 사용 트랜잭션 없이 진행
NOT_SUPPORTED 해당 트랜잭션 보류 트랜잭션 없이 진행
NEVER 예외 발생 트랜잭션 없이 진행
  • 진행중인 트랜잭션이 있을 때 REQUIRES_NEW와 NESTED의 차이
    • REQUIRES_NEW - 각각의 트랜잭션이 롤백되더라도 서로 영향을 주지 않는다.
    • NESTED - 중첩 트랜잭션 내부에서 롤백 발생시 해당 중첩 트랜잭션의 시작 지점까지만 롤백 된다. 중첩 트랜잭션은 부모 트랜잭션이 커밋될 때 같이 커밋된다.

(참고) 고객 주문 + 주문 내역을 기록해주는 기능을 개발한다면?

  • 중요한 일(주문 내역), 중요하지 않은 일(로그)을 별개의 트랜잭션으로 관리
  • 주문이 실패하면 로그도 기록되지 않아야 하지만, 주문이 성공했는데 로그 실패 때문에 전체 롤백이 되면 안된다.
  • 부모 트랜잭션의 커밋, 롤백 -> 중첩 트랜잭션에 영향을 줌
  • 중첩된 트랜잭션의 커밋, 롤백 -> 부모 트랜잭션에 영향을 주지 않음
  • 주문은 부모 트랜잭션으로 설정하고, 로그는 중첩 트랜잭션으로 구현

참고자료