트랜잭션의 전파속성과 격리수준은 데이터베이스 트랜잭션을 안정적으로 운영하기 위해 필수적인 요소이다.
특히 복잡한 비즈니스 로직을 구현할 때, 트랜잭션 전파 속성과 격리 수준을 적절히 설정하는 것이 매우 중요하다.

이들의 설정이 잘못되면 데이터의 일관성이 깨지고, 시스템의 신뢰도를 크게 떨어뜨린다.
따라서 이에 대해 적절히 이해하고 설정하는 것이 중요하다.

먼저 트랜잭션에 대해 알아보고, 전파속성과 고립수준에 대해 자세히 알아보자.


Transaction(트랜잭션)

  • Transaction(트랜잭션)
    • 데이터베이스 관리 시스템(DBMS)에서 하나의 논리적 작업 단위
    • 데이터베이스의 상태를 변화시키는 작업의 집합
    • 여러 개의 작업(쿼리, 데이터 수정 등)이 모여 하나의 작업으로 처리되는 것을 보장한다.

ACID: 트랜잭션의 4가지 특성

  1. Atomicity(원자성)
    • 모든 작업이 전부 수행되거나 전혀 수행되지 않아야 한다는 특성
    • 중간에 실패하면 전체 작업이 취소(rollback)되어 데이터가 일관된 상태로 되돌아간다.
      • 예: 은행 계좌 이체 시, 출금과 입금이 모두 성공해야만 완료된다.
  2. Consistency(일관성)
    • 트랜잭션 수정 전후에 데이터베이스의 상태가 일관성을 유지해야 한다.
    • 데이터 무결성 규칙이 항상 지켜져야 한다는 의미
      • 예: A계좌에서 100만원을 B걔좌로 이체할 경우, 전체 자산 총합은 변하지 않아야 한다.
  3. Isolation(격리성)
    • 동시에 실행되는 트랜잭션이 서로 영향을 주지 않아야 한다.
    • 여러 트랜잭션이 동시에 실행될 때도 각 트랜잭션이 독립적으로 처리된다.
    • 격리 수준은 READ UNCOMMITTED, READ COMMITTIED, REPEATABLE READ, SERIALIZABLE 등이 있다.
  4. Durability(지속성)
    • 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 저장되어야 한다.
    • 시스템 장애가 발생하더라도 데이터가 손실되지 않아야 한다.
    • 이 특성은 디스크 저장, 로그 기록 등을 통해 보장된다.

트랜잭션의 시작과 종료

트랜잭션에는 시작 지점과 종료시점이 존재한다.
시작 방법은 1개이지만, 끝나는 방법은 커밋(commit)과 롤백(rollback)으로 2가지가 있다.

트랜잭션의 시작

트랜잭션은 하나의 Connection을 가져와 사용하다가 닫는 사이에 일어난다.
트랜잭션의 시작과 종료는 Connection 객체를 통해 이뤄지기 때문이다.

JDBC의 기본 설정은 자동 커밋 옵션(DB 작업 수행 직후 바로 커밋을 하는 기능)이 활성화되어 있는 것이다.
따라서 JDBC에서 트랜잭션을 시작하려면 자동 커밋 옵션을 false로 해주어야 하는데, 그러면 새로운 트랜잭션이 시작되게 만들 수 있다.

public void executeQuery() throws SQLException {
	Connection connection = dataSource.getConnection();
    connection.setAutoCommit(false);
    // 트랜잭션 시작
    
    ...   
}

스프링 이용 시, 내부적으로 커넥션을 갖고있는 추상화된 트랜잭션 매니저를 이용하게 된다.
트랜잭션은 다음과 같이 시작되고, 자동 커밋 옵션 변경등의 작업은 트랜잭션 매니저 내부에서 진행된다.

public void executeQuery() throws SQLException {
	TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}

트랜잭션의 종료

하나의 트랜잭션이 시작되면 commit() 또는 rollback()이 호출될 때 까지가 하나의 트랜잭션으로 묶인다.
이처럼 setAutoCommit(false)으로 트랜잭션의 시작을 선언하고 commit() 또는 rollback()으로 트랜잭션을 종료하는 작업트랜잭션의 경계설정이라고 한다.
트랜잭션의 경계는 하나의 Connection을 통해 진행되므로 트랜잭션의 경계는 하나의 커넥션이 만들어지고 닫히는 범위 안에 존재한다.

public void executeQuery() throws SQLException {
	TransactionStatus status = transactionManager.getTransaction(new DefaultTrnasactionDefinition());
    // 트랜잭션 시작
    
    try {
    	// 쿼리 실행
        ...
        
        transactionManager.commit(status);
    } catch (Exception e) {
    	transactionManager.rollback(status);
    }
    
}

트랜잭션 전파속성(Transaction Propagation)

  • 트랜잭션 전파 속성(Transaction Propagation)
    • 하나의 트랜잭션이 다른 트랜잭션을 호출할 때 어떻게 동작할지를 정의
    • 이미 트랜잭션이 진행중일 때 추가 트랜잭션 진행을 어떻게 할지 결정하는 것
    • 트랜잭션의 일관성과 안전성을 보장하는데 중요한 역할을 수행한다.

종류

  • REQUIRED (가장 많이 사용)
    • 기존 트랜잭션이 있으면 합류하고, 없으면 새로운 트랜잭션을 생성한다.
    • "트랜잭션이 필요해. 있으면 쓰고, 없으면 만들게"
  • REQUIRES_NEW
    • 항상 새로운 트랜잭션을 생성한다.
    • 기존 트랜잭션이 있더라도 이를 일시 중지하고 새로운 트랜잭션을 시작한다.
    • 독립적인 트랜잭션이 필요할 때 사용
    • "나는 무조건 새로운 트랜잭션이 필요해!"
  • SUPPORTS
    • 기존 트랜잭션이 있으면 합류하고, 없으면 없이 동작한다.
    • "트랜잭션이 있으면 좋고, 없어도 괜찮아"
  • NOT_SUPPORTED
    • 트랜잭션 없이 동작한다.
    • 기존 트랜잭션이 있으면 이를 일시중지한다.
  • MANDATORY
    • 기존 트랜잭션이 반드시 있어야 한다.
    • 없으면 예외를 발생시킨다.
  • NEVER
    • 트랜잭션이 있으면 예외를 발생시킨다.
    • 트랜잭션 없이 동작한다.
  • NESTED
    • 기존 트랜잭션 내에서 중첩 트랜잭션을 생성한다.
    • 중첩 트랜잭션은 부모 트랜잭션에 종속된다.
      • 부모 트랜잭션에 예외가 발생하면 자식 트랜잭션도 rollback 한다.
      • 하지만 자식 트랜잭션에 예외가 발생하더라도 부모 트랜잭션은 rollback 하지 않는다.

트랜잭션 격리 수준(Isolation Level)

  • 트랜잭션 고립 수준(Isolation Level)
    • 동시에 여러 트랜잭션이 처리될 때, 트랜잭션끼리 얼마나 서로 고립되어야 하는지를 정의

Read Phenomena (읽기 이상 현상)

트랜잭션 사용 시 발생할 수 있는 이상현상들

  1. Dirty Reads
    • 동시에 진행되고 있는 다른 트랜잭션(아직 커밋하지 않은 상태)에서 변경한 데이터를 현재 진행 중인 트랜잭션에서 읽어들이는 것
    • 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 현상
      • 예: A가 데이터 수정 중인데 B가 수정 중인 데이터를 읽음
  2. Non-Repeatable Read
    • 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때 결과가 다른 현상
    • 하나의 트랜잭션 중 읽어 들였던 특정 row의 값을 같은 트랜잭션 내에서 다시 읽어 들이는데 주간에 변경사항이 생겨(실제로 commit이 된 변경사항) 결과값이 다르게 나오는 현상
      • 예: A가 데이터를 읽고 있는 중에 B가 데이터를 수정하고 커밋
  3. Phantom Read
    • 한 트랜잭션 내에서 같은 쿼리를 두 번 실행했을 때, 첫 번째 쿼리에서 존재하지 않던 유령 레코드가 두 번째 쿼리에서 나타나는 현상 (없던 데이터가 생기는 현상)
      • 예: A가 특정 조건으로 검색 중에 B가 새로운 레코드를 삽입

이상 현상이 발생하면 안 되지만, 이런 이상 현상을 모두 제어하려면 제약사항이 많아져 동시 처리 가능한 트랜잭션 수가 줄어들게 된다.
이를 해결하기 위해, 일부 이상 현상은 허용하는 몇가지 Level을 만들어 개발자가 필요에 따라 적절하게 선택할 수 있도록 4단계로 그 수준을 분리했다.

종류

  • READ UNCOMMITTED (Level 0) (커밋되지 않은 읽기)
    • 다른 트랜잭션의 변경사항이 커밋되지 않아도 읽을 수 있음
    • Dirty Read 발생 가능
    • 정합성이 떨어지지만 성능은 가장 좋음
  • READ COMMITTED (Level 1) (커밋된 읽기)
    • 커밋된 데이터만 읽을 수 있음
    • Dirty Read 방지
    • Non-Repeatable Read 발생 가능
      • Oracle의 기본 격리 수준
  • REPEATABLE READ (Level 2) (반복 가능한 읽기)
    • 트랜잭션 내에서 같은 쿼리를 실행하면 항상 같은 결과
    • Non-Repeatable Read 방지
    • Phantom Read 발생 가능
      • MYSQL의 기본 격리 수준
  • SERIALIZABLE (Level 3) (직렬화 기능)
    • 가장 높은 격리 수준
    • 완벽한 읽기 일관성 제공
    • 성능이 가장 떨어짐
    • 실제로는 거의 사용되지 않음


Reference

profile
Good Luck!

0개의 댓글

Powered by GraphCDN, the GraphQL CDN