java

Java Transaction

rockettttman 2020. 12. 1. 21:26

Java에서의 transaction 처리

트랜잭션이란?

  • 쪼개질 수 없는 업무 처리의 단위

트랜잭션의 성질

  • 원자성(Automicity) : 한 트랜잭션 내에서 실행한 작업은 하나로 간주한다. 즉 모두 성공 혹은 모두 실패다.
  • 일관성(Consistency) : 트랜잭션은 일관성 있는 데이타베이스 상태를 유지한다. (data integrity 만족 등.)
  • 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않게 격리한다.
  • 지속성(Durability) : 트랜잭션이 성공적으로 실행되면 결과는 항상 저장된다.

트랜잭션의 문제점

  1. Dirty Read
    트랜잭션 A가 어떤 값을 1에서 2로 변경했다. 아직 커밋하지 않은 상황에서, 트랜잭션B가 같은 값을 읽는다면? 트랜잭션 B는 2를 읽을 것이다. 이 때 A가 롤백된다면? 트랜잭션B는 잘못된 값을 읽은 것이 된다. 요약하면, 아직 수정 중인 데이터에 접근을 허용할 경우 발생하는 데이터 불일치이다.
  2. Non-Repeatable Read
    트랜잭션 A가 값 1을 읽었다. 이후 A는 같은 쿼리를 또 실행할 예정인데, 그 사이에 트랜잭션 B가 값 1을 2로 바꾸고 커밋하면? A는 같은 쿼리 두번을 날렸으나, 두 쿼리의 결과가 다르게 된다. 요약하면, 한 트랜잭션에서 같은 쿼리를 두번 실행했을 때 발생하는 데이터 불일치이다
  3. Phantom Read
    트랜잭션 A가 어떤 조건을 사용하여 특정 범위의 값들 [0,2,4,6,8]을 읽었다. 이후 A는 같은 쿼리를 실행할 예정인데, 그 사이에 트랜잭션 B가 같은 테이블에 값[1,3,5]을 추가하고 커밋했다면? A는 같은 쿼리를 다시 실행했지만 두번째 결과는 [1,2,3,4,5,6,8]을 얻는다. 요약하면, 한 트랜잭션에서 일정 범위의 레코드를 두번 이상 읽을 때 발생하는 데이터 불일치이다.

트랜잭션의 격리 수준

이와 같은 문제와 연관된 트랜잭션 격리 수준은 총 4가지이다. 수준(Level)이 높을수록 성능은 떨어지므로, 높은 격리 수준을 채택하는 것이 항상 좋은 방법은 아니다. 적절한 수준을 채택하는 것이 좋다.

  1. Level0 — Read Uncommited
    다른 트랜잭션이 커밋하지 않은 레코드에 접근이 가능하다. 가장 낮은 수준의 트랜잭션 격리이다. 그래서 앞서 언급한 3가지 문제가 모두 발생한다. 그러나 성능은 가장 뛰어나다.
  2. Level1 — Read Commited
    항상 커밋된 레코드만 읽는다. 그러므로 아직 커밋되지 않는 레코드를 읽다가 발생하는 Dirty Read는 방지된다. 하지만 나머지 두가지 문제는 예방할 수 없다. 대부분의 DBMS에서 기본적으로 채택하고 있는 방식으로 알려져 있다.
  3. Level2 — Repeatable Read
    특정 트랜잭션이 읽은 레코드에 대하여 그 트랜잭션이 끝날 때까지 다른 트랜잭션이 수정/삭제하는 것을 방지한다. 한 트랜잭션에서 같은 쿼리를 두번 실행했을 때 결과가 다른 문제인 Non-Repeatable-Read가 예방된다. 하지만 특정 범위를 지정한 것도 아니고, 레코드 생성을 방지한 것은 아니므로 Phantom Read는 예방할 수 없다. Oracle에서 종종 사용하는 SELECT … FOR UPDATE 구문이 이 방식이다.
  4. Level3 — Serializable Read
    어떤 트랜잭션이 여러개의 레코드를 조회할 때, 그 트랜잭션이 끝날 때까지 조회 범위에 새로운 레코드를 조회/생성/수정/삭제 하는 것을 방지한다. 모든 동시성 관련 문제를 예방할 수 있으나, 성능이 가장 안좋다.

출처: https://preamtree.tistory.com/154 [Preamtree의 행복로그]

 

Spring에서의 Transaction 처리

@Transactional(propagation=Propagation.REQUIRED)

Transactional의 속성

전파레벨

  • REQUIRED : 이미 시작된 트랜잭션(부모 트랜잭션)이 있으면 참여하고 없으면 새로 시작한다. (디폴트)
  • SUPPORTS : 이미 시작된 트랜잭션이 있으면 참여하고 없으면 트랜잭션 없이 진행한다.
  • REQUIRED_NEW : 부모 트랜잭션을 무시하고 항상 새로운 트랜잭션을 시작한다.
  • NESTED : 메인 트랜잭션 내부에 중첩된 트랜잭션을 시작한다. 부모 트랜잭션 결과에는 영향을 받지만, 자신의 트랜잭션 결과는 부모에게 영향을 미치지 않는다.
  • NOT_SUPPORTED : 트랜잭션을 사용하지 않게 한다. 이미 진행 중인 트랜잭션이 있으면 보류시킨다.
  • MANDATORY : REQUIRED와 비슷하게 이미 시작된 트랜잭션이 있으면 참여하지만, 트랜잭션이 시작된 것이 없으면 예외를 발생시킨다. (혼자서 독립적으로 트랜잭션을 진행하면 안되는 경우에 사용)
  • NEVER : 트랜잭션을 사용하지 않게 하며 이미 시작된 트랜잭션이 있으면 예외를 발생시킨다.

격리레벨

  • DEFAULT : 기본 설정, 기본 격리 수준
  • SERIALIZABLE : 가장 높은 격리, 성능 저하 가능성 있음
  • READ_UNCOMMITED : 커밋되지 않은 데이터 읽을 수 있음
  • READ_COMMITED : 커밋된 데이터에 대해 읽기 허용
  • REPEATABLE_READ : 동일 필드에 대한 다중 접근 시 동일 결과 보장