트랜잭션 (Transaction)

duckbill413·2023년 1월 21일
0

Spring JPA

목록 보기
3/7
post-thumbnail

트랜잭션 (transaction)

트랜잭션 특징

트랜잭션 ACID

원자성, 일관성, 고립성, 지속성

원자성 (Atomicity)

  • 사전적 의미 원자성은 트랜잭션과 관련된 작업들이 부분적으로 실행되다가 중단되지 않는 것을 보장하는 능력이다. 즉, 작업이 중간 단계까지만 실행되고 실패하는 일이 없도록 하는 것이다.
  • 기능적 의미 원자적 연산을 보장해야 한다. ⇒ All or Nothing MySQL에서는 MVCC를 통해 지원해 준다. 트랜잭션이 Atomicity한 단위가 된다.

일관성 (Consistency)

  • 사전적 의미 트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지하는 것
  • 기능적 의미 트랜잭션의 종료되었을 때 데이터 무결성 보장된다. 제약조건을 통해 ⇒ 유니크 제약, 외래키 제약 등

고립성(Isolation)

  • 사전적 의미 트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것을 의미한다. 이것은 트랜잭션 밖에 있는 어떤 연산도 중간 단계의 데이터를 볼 수 없음을 의미한다. 성능관련 이유로 인해 이 특성은 가장 유연성 있는 제약 조건이다.
  • 기능적 의미 트랜잭션은 서로 간섭하지 않고 독립적으로 동작한다. ⇒ 하지만 많은 성능을 포기해야 하므로 개발자가 제어 가능 ⇒ 트랜잭션 격리레벨을 통해 제거 (Undo logMVCC)

지속성(Durability)

  • 사전적 의미 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함을 의미한다. 시스템 문제, DB 일관성 체크 등을 하더라도 유지되어야 함을 의미한다. 전형적으로 모든 트랜잭션은 로그로 남고 시스템 장애 발생 전 상태로 되돌릴 수 있다. 트랜잭션은 로그에 모든 것이 저장된 후에만 commit 상태로 간주될 수 있다.
  • 기능적 의미 완료된 트랜잭션은 유실되지 않는다. WAL(Write ahead logging)을 통해서 이루어진다.
    • WAL (Write ahead loggin) 트랜잭션이 일어나기 전에 로그를 미리 기록하여 트랜잭션 undo, redo를 할 수 있도록 한다. 변경을 로깅한 후에만, 즉 변경 내용을 설명한는 로그 레코드를 영구적 저장소에 먼저 기록한 후에 데이터 파일(테이블과 인덱스가 있는)의 변경 내용을 작성해야 한다는 것이다. 위의 내용을 준수한 경우 충돌 발생 시 로그를 사용하여 데이터베이스를 복수할 수 있으므로 트랜잭션 커밋마다 데이터 페이지를 디스크에 쓸 필요가 없다. 데이터 페이지에 적용되지 않은 변경 내용은 로그 레코드에서 실행 취소가 가능하다.

트랜잭션 컨트롤

  • Transactional 어노테이션 사용해서 선언적으로 제어
  • Transaction Template를 사용해서 직접 제어

Commit

하나의 트랜잭션이 성공적으로 끝났고, DB가 일관성 있는 상태일 때 이를 알려주기 위해 사용되는 연산


Rollback

하나의 트랜잭션 처리가 비정상적으로 종료되어 트랜잭션의 원자성이 깨진 경우 rollback을 통해 처음 상태로 되돌아가는 것

Spring에서의 트랜잭션 3가지 핵심 기술

1. 트랜잭션(Transaction) 동기화

트랜잭션 동기화는 트랜잭션을 시작하기 위한 Connection 객체를 특별한 저장소에 보관해두고 필요할 때 꺼내쓸 수 있도록 하는 기술이다. 트랜잭션 동기화 저장소는 작업 쓰레드마다 Connection 객체를 독립적으로 관리하기 때문에, 멀티쓰레드 환경에서도 충돌이 발생할 여지가 없다.

2. 트랜잭션 추상화

Spring은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공한다. 이를 이용하여 애플리케이션에 각 기술마다(JDBC, JPA, Hibernate 등) 종속적인 코드를 이용하지 않고도 일관되게 트랜잭션을 처리할 수 있도록 해주고 있다.

3. AOP를 이용한 트랜잭션 분리

트랜잭션 코드와 비지니스 로직 코드가 복잡하게 얽혀있는 코드가 있을때 트랜잭션 코드와 비지니스 로직 코드를 분리하기 위하여 해당 로직만을 클래스 밖으로 빼내서 별도의 모듈로 만드는 AOP를 고안 및 적용하게 되었다. 이를 Spring에서 @Transactional을 이용한다.

Spring 트랜잭션의 세부 설정

전파 속성 (Propagation)

Spring이 지원하는 전파 속성은 7가지가 있다.

  1. REQUIRED
    • 디폴트 속성으로써 모든 트랜잭션 매니저가 지원
    • 이미 시작된 트랜잭션이 있으면 참여하고 없으면 새로 시작
    • 하나의 트랜잭션이 시작된 후 다른 트랜잭션 경계가 설정된 메소드를 호출하면 같은 트랜잭션으로 묶임
    • REQUIRED는 가장 자연스러운 트랜잭션 전파 방식으로 주로 사용되는 속성이다.
  2. SUPPORTS
    • 이미 시작된 트랜잭션이 있으면 참여하고, 그렇지 않으면 트랜잭션 없이 진행함
    • 트랜잭션이 없어도 해당 경계 안에서 Connection 객체나 하이버네이트의 Session 등은 공유 할 수 있음
  3. MANDATORY
    • 이미 시작된 트랜잭션이 있으면 참여하고, 없으면 새로 시작하는 대신 예외를 발생시킴
    • 혼자 독립적으로 트랜잭션을 진행하면 안되는 경우에 사용
  4. REQUIRES_NEW
    • 항상 새로운 트랜잭션을 시작해야 하는 경우에 사용
    • 이미 진행중인 트랜잭션이 있으면 이를 보류하고 새로운 트랜잭션을 만들어 시작
  5. NOT_SUPPORTED
    • 이미 진행중인 트랜잭션이 있으면 이를 보류시키고, 트랜잭션을 사용하지 않도록 함
  6. NEVER
    • 이미 진행중인 트랜잭션이 있으면 예외를 발생시키며, 트랜잭션을 사용하지 않도록 강제함
  7. NESTED
    • 이미 진행중인 트랜잭션이 있으면 중첩(자식) 트랜잭션을 시작함
    • 중첩 트랜잭션은 트랜잭션 안에 다시 트랜잭션을 만드는 것으로, 독립적인 트랜잭션을 만드는 REQUIRED_NEW와는 다르다.
    • NESTED에 의한 중첩 트랜잭션은 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만, 자신의 커밋과 롤백은 부모 트랜잭션에 영향을 주지 않는다.

격리 수준 (Isolation)

동시에 여러 트랜잭션이 진행될 때 트랜잭션의 작업 결과를 여타 트랜잭션에게 어떻게 노출할 것인지를 결정한다.

Isolation 5가지 수준

  1. DEFAULT
    • 사용하는 데이터 엑세스 기술 또는 DB 드라이버의 디폴트 설정에 따름
    • 일반적으로 드라이버 격리 수준은 DB의 격리 수준을 따르며, 대부분의 DB는 READ_COMMITED를 기본 격리 수준으로 가짐
  2. READ_UNCOMMITTED
    • 가장 낮은 격리수준으로써 하나의 트랜잭션이 커밋되기 전에 그 변화가 다른 트랜잭션에 그대로 노출되는 문제를 가진다.
    • 가장 빠른 속도를 가지므로 데이터의 일관성이 떨어지더라도 의도적으로 사용될 수 있다.
    • Dirty Read, Phantom Read, Non-Repeatable Read 문제
  3. READ_COMMITTED
    • Spring은 기본 속성이 DEFAULT이며, DB는 일반적으로 READ_COMMITED가 기본 속성으로 사용된다.
    • 가장 많이 사용되는 속성이다.
    • READ_UNCOMMITED와 달리 다른 트랜잭션이 커밋하지 않은 정보는 읽을 수 없다.
    • 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 있어서 처음 트랜잭션이 같은 로우를 다시 읽을 때 다른 내용이 발견딜 수 있음
    • Phantom Read, Non-Repeatable Read 문제
  4. REPEATABLE_READ
    • 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 없도록 막아주지만 새로운 로우를 추가하는 것은 막지 않는다.
    • 따라서 SELECT로 조건에 맞는 로우를 전부 가져오는 경우 트랜잭션이 끝나기 전에 추가된 로우를 발견할 수 없다.
    • Phantom Read 문제
  5. SERIALIZABLE
    • 가장 강력한 트랜잭션 격리 수준으로, 이름 그대로 트랜잭션을 순차적으로 진행시켜준다.
    • SERIALIZABLE 은 여러 트랜잭션이 동시에 같은 테이블의 정보를 엑세스 할 수 없다.
    • SERIALIZABLE 은 가장 안전하지만 가장 성능이 떨어지므로 극단적으로 안전한 작업이 필요한 경우가 아니라면 사용해서는 안된다.

트랜잭션 격리수준에 따른 문제점

더티 리드(Dirty Read)

더티 리드는 특정 트랜잭션에 의해 데이터가 변경되었지만, 아직 커밋되지 않은 상황에서 다른 트랜잭션이 해당 변경 사항을 조회할 수 있는 문제를 말한다.

반복 불가능한 조회(Non-Repeatable Read)

같은 트랜잭션 내에서 같은 데이터를 여러번 조회했을 때 읽어온 데이터가 다른 경우를 의미한다.

팬텀 리드(Phantom Read)

Non-Repeatable Read의 한 종류로 조회해온 결과의 행이 새로 생기거나 없어지는 현상이다.


Spring transaction Test

  1. 트랜잭션은 트랜잭션 메소드가 끝날때 커밋이 되어 반영되게 된다.
    • 예시
      @Transactional
      public void putBookAndAuthor(){
          Book book = new Book();
          book.setName("JPA 시작하기");
          bookRepository.save(book);
      
          Author author = new Author();
          author.setName("martin");
          authorRepository.save(author);
      }
      다음과 같은 코드가 실행 되었을때 save 메서드가 실행 되더라도 즉시 실행되는 것이 아니라 메서드가 모두 종료되는 마지막에서 트랜잭션이 이루어지게 된다. 즉, 중간에서 데이터를 조회해도 출력되지 않는다.
  2. 트랜잭션 원자성 테스트
    • 예시
      @Test // MEMO: Transaction 의 all or nothing 원자성 test
      void transactionErrorTest(){
          try {
              bookService.putBookAndAuthorError();
          }
              catch (RuntimeException e){
              System.out.println(">>> " + e.getMessage());
          }
          System.out.println("books : " + bookRepository.findAll());
          System.out.println("authors : " + authorRepository.findAll());
      }
      만약 트랜잭션 수행 중 RuntimeError과 같은 에러가 발생한다면 트랜잭션은 rollback을 통해 트랜잭션 이전 상태로 되돌아간다.

      Checked / UnChecked Exception

      Exception 에는 Checked 와 UnChecked Exception 이 존재한다. RuntimeException 을 상속하면 UnChecked Exception, 상속하지 않으면 Checked Exception 이며 Checked Exception 은 개발자가 모든 책임을 지게 된다.
      • CheckedException 은 발생시 rollback 이 수행되지 않는다. 즉, 개발자가 catch 문에서 직접 rollback 등의 기능을 구현해야 한다.
      • UnCheckedException(RuntimeException) 은 발생시 transaction 에서 rollback 이 수행된다.
  3. 트랜잭션 격리성(isolation) 테스트
    • Isolation.READ_UNCOMMITTED : 트랜잭션이 수행되기전 값을 읽어올 수 있다.
profile
같이 공부합시다~

0개의 댓글