본문 바로가기

데이터베이스

트랜잭션과 Spring의 선언적 트랜잭션에 대하여

트랜잭션이란?

더이상 나눌 수 없는 논리적인 작업 단위를 의미하며, 데이터베이스에서는 상태를 변경시키기 위해 수행하는 작업 단위이다. 

스프링에서 선언적 트랜잭션 

스프링에서는 트랜잭션 처리를 지원하는데, 그 중 한 방법으로 @Transactional 어노테이션을 클래스 혹은 메서드 위에 추가할 수 있다. 
이를 선언적 트랜잭션이라고 부른다. 

트랜잭션의 성질 (ACID)

데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질

Atomicity - 원자성

  • 더 이상 쪼갤 수 없는 원자의 성질
  • 한 트랜잭션 내에서 실행한 작업들은 하나로 간주  
  • COMMIT이거나, ROLLBACK 이거나

Consistency - 일관성

  • 트랜잭션이 성공적으로 완료되면, 일관성 있는 데이터베이스의 상태를 유지함
  • ex) 트랜잭션이 수행된 후의 데이터 타입이 변경되지 않음

Isolation - 격리성

  • 트랜잭션 수행시 다른 트랜잭션의 작업이 끼어들지 못하도록 보장
  • 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않도록 격리
  • 트랜잭션끼리는 서로를 간섭할 수 없음
  • (DB에 따라 설정이 가능함)

Durability - 지속성

  • 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함
  • commit을 하면 현재 상태는 영원히 보장됨

격리성과 관련되어 발생할 수 있는 문제점

다수의 트랜잭션이 동시에 실행되는 환경에서 트랜잭션의 격리 수준을 고려해야하는데, 
이 격리성과 관련되어 발생할 수 있는 몇가지 문제점을 먼저 살펴보자. 

1. Dirty Read

다른 트랜잭션에 의해 수정됐지만 아직 커밋되지 않은 데이터를 읽는 것

트랜잭션 A가 어떤 값을 "아마찌"에서 "newwisdom"로 변경 후, 커밋하지 않은 상황에서 
트랜잭션 B가 같은 값을 읽을 경우 트랜잭션 B는 "newwisdom"를 조회하게 된다. 

만약 B가 "newwisdom"을 조회한 후 A가 롤백이 될 경우 트랜잭션 B는 잘못된 값을 읽게 된다. 
즉, 트랜잭션이 완료되지 않은 상황에서 데이터 접근을 허용할 경우 발생할 수 있는 데이터 불일치 현상이다. \

2. None-Repeatable Read

트랜잭션 A가 "아마찌"를 읽었고, 이후 A가 같은 쿼리를 또 실행할 예정인데, 그 사이에 트랜잭션 B가 "아마찌"를 "newwisdom"으로 수정하고 COMMIT하면 A가 한 트랜잭션 내에서 같은 쿼리를 두번 날리는 사이에 두 쿼리의 결과가 다르게 되어 버린다. 

즉 한 트랜잭션에서 같은 쿼리 두번 실행에 대한 데이터 불일치 현상이다. 
Dirty Read보다 발생률이 적고 일반적인 웹 어플리케이션에서는 크은 문제가 되지는 않지만, 금전적인 처리가 얽혀있다면 큰 문제가 될 수 있다. 
ex) 입금/출금 처리가 계속 진행되는 트랜잭션들이 있고, 입금 총합을 보여주는 트랜잭션이 있는 여러개의 트랜잭션이 있는 상황에서, 총합을 계산하는 select 쿼리가 실행될 때마다 다른 결과값을 가져온다. 

3. Phantom Read

트랜잭션 A가 어떤 조건절을 사용해 특정 범위의 값들을 읽었다. 
이 후 A가 같은 쿼리를 실행할 예정인데, 그 사이에 트랜잭션 B 가 같은 테이블에 값을 추가해버리면 A가 같은 쿼리 두 번을 날리는 사이에 두 쿼리의 결과가 다르게 된다. 

즉 한 트랜잭션에서 일정 범위의 레코드를 두 번 이상 읽을 때 발생하는 데이터 불일치이다. 
첫번째 쿼리에서 없던 유령(Phantom) 레코드가 두 번째 쿼리에서 나타나는 현상이다.

@Transactional 속성

isolation (격리 수준)

트랜잭션에서 일관성이 없는 데이터를 어느정도 허용할지의 수준을 의미한다.

DEFAULT

  • 기본 설정 
  • 즉 DB의 격리 Level을 따름
  • 우리가 사용하는 Mysql InnoDB 의 기본 설정은 REPEATABLE READ

READ_UNCOMMITED (level0)

  • COMMIT 되지 않은(아직 트랜잭션 처리 중인) 데이터에 대한 읽기를 허용
  • 즉 어떤 사용자가 "아마찌"라는 데이터를 "newwisdom"으로 변경하는 동안 다른 사용자는 아직 완료되지 않은 (Uncommitted) 데이터를 읽을 수 있음
  • Dirty Read, Non-Repeatable Read, Phantom Read 발생

READ_COMMITTED (level 1)

  • 트랜잭션이 COMMIT된 데이터에 대해서만 읽기를 허용
  • 어떤 사용자가 "아마찌"라는 데이터를 "newwisdom"이라는 데이터로 변경하는 동안 다른 사용자는 해당 데이터에 접근할 수 없음
  • 위에서 발생한 Dirty Read 문제점을 해결할 수 있음
  • Non-Repeatable Read, Phantom Read 발생

REPEATABLE_READ (level 2)

  • 트랜잭션이 완료될 때 까지 SELECT 문이 조회하는 데이터들에 대핸 LOCK이 걸려 다른 사용자는 해당 영역의 데이터에 대한 수정이 불가능
  • 선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신하거나 삭제가 불가능해, 한 트랜잭션에서 같은 쿼리 두번 실행에 대한 데이터의 일관성을 보장
  • 위에서 발생하는 Non-Repeatable Read 문제점을 해결할 수 있음
  • Phantom Read 발생
    • 하지만 InnoDB 스토리지 엔진에서는 갭 락과 넥스트 키 락 덕분에 해당 격리 수준에서도 이미 Phantom Read가 발생하지 않음

SERIALIZABLE (level 3)

  • 동시에 같은 테이블의 정보를 접근할 수 없음
  • 트랜잭션을 순차적으로 수행하는 것과 다를 바 없어 성능이 매우 저하됨
  • Phantom Read 문제점을 해결할 수 있음

propagation (전파 옵션)

트랜잭션 동작 도중 다른 트랜잭션을 호출하는 상황에 선택할 수 있는 옵션이다.

REQUIRED

  • 기본 설정
  • 부모 트랜잭션 내에서 실행하며, 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성

REQUIRED_NEW

  • 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션을 생성 

SUPPORTS

  • 이미 시작된 트랜잭션이 있으면 참여, 그렇지 않으면 트랜잭션 없이 진행

MANDATORY

  • REQUIRED와 비슷하게 이미 시작된 트랜잭션이 있으면 참여
  • 반면 트랜잭션이 시작된 것이 없으면 예외를 발생시킴
  • 혼자서는 독립적으로 트랜잭션을 진행하면 안되는 경우에 사용

NOT_SUPPORTED

  • 트랜잭션을 사용하지 않게함
  • 이미 진행중인 트랜잭션이 있으면 보류하고 메서드 실행

NEVER

  • 트랜잭션을 사용하지 않도록 강제
  • 이미 진행중인 트랜잭션이 존재한다면 예외 발생

NESTED

  • 이미 진행중인 트랜잭션이 있으면 중첩 트랜잭션을 시작
  • 중첩 트랜잭션이란 트랜잭션 안에 다시 트랜잭션을 만드는 것을 의미
  • 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만 자신의 커밋과 롤백은 부모 트랜잭션에게 영향을 주지 않음
  • 부모 트랜잭션이 없을 경우 REQUIRED와 동일히 작동

readOnly 속성

  • 트랜잭션을 읽기 전용으로 설정 가능 (SELECT)
  • 성능 최적화, 특정 트랜잭션 작업 안에서 쓰기 작업이 일어나는 것을 의도적으로 방지하기 위해 사용
  • 읽기 전용 트랜잭션이 시작된 이후 CUD 같은 쓰기 작업이 진행되면 예외가 발생

트랜잭션 롤백 예외 - (rollback-for, rollbackFor, rollbackForClassName) 

  • 선언적 트랜잭션에서는 런타임 예외가 발생하면 ROLLBACK
  • 예외가 발생하지 않거나, Checked Exception이 발생하면 COMMIT
  • 스프링에서는 Data Access 기술의 예외는 런타임 예외로 전환되어 런타임 예외만 롤백 대상으로 삼음
  • 이 기본 동작 방식을 바꿀 수 있는 옵션

timeout

  • 지정 시간 내에 해당 메서드 수행이 완료되지 않은 결루 ROLLBACK
  • 기본설정은 -1로 No Timeout

참고자료

'데이터베이스' 카테고리의 다른 글

Database Connection Pool (Database)  (0) 2022.02.18
[Real MySQL 8.0] 5장. 트랜잭션과 잠금  (0) 2021.12.28