직렬화 가능한 트랜잭션 (Serializable Transactions) 사용

들어가기 전에

Oracle 데이터베이스는 데이터의 무결성을 보장하기 위해 다양한 격리 수준을 제공합니다. 그중에서도 직렬화 가능(Serializable) 격리 수준은 가장 강력한 수준으로, 동시 트랜잭션들이 서로 간섭하지 않고 순차적으로 실행되는 것과 같은 결과를 보장합니다.

본 가이드에서는 직렬화 가능 트랜잭션의 개념, 사용 방법, 장점 및 주의사항에 대해 자세히 알아보겠습니다. 실무에 바로 적용 가능한 예시 코드와 실행 결과를 통해 완벽하게 이해할 수 있도록 돕겠습니다.

직렬화 가능 트랜잭션이란 무엇인가?

직렬화 가능 트랜잭션은 여러 트랜잭션이 동시에 실행되더라도, 각 트랜잭션이 마치 다른 트랜잭션들이 완료된 후에 순차적으로 실행되는 것처럼 보이도록 보장합니다. 이는 데이터베이스의 일관성을 유지하고 동시성 문제(예: Dirty Read, Non-Repeatable Read, Phantom Read)를 방지하는 데 매우 중요합니다.

직렬화 가능 트랜잭션의 장점

  • 데이터 무결성 보장: 가장 강력한 격리 수준을 제공하여 데이터 일관성을 극대화합니다.
  • 동시성 문제 방지: Dirty Read, Non-Repeatable Read, Phantom Read와 같은 문제를 원천적으로 차단합니다.
  • 복잡한 비즈니스 로직 처리: 여러 테이블에 걸친 복잡한 트랜잭션에서도 데이터의 정확성을 보장합니다.

직렬화 가능 트랜잭션 사용 방법

Oracle 데이터베이스에서 직렬화 가능 격리 수준을 사용하려면 다음과 같이 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 문을 트랜잭션 시작 시에 실행해야 합니다.


   -- 트랜잭션 시작
   SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

   -- SQL 문 실행
   SELECT * FROM accounts WHERE account_id = 100;
   UPDATE accounts SET balance = balance - 100 WHERE account_id = 100;
   UPDATE accounts SET balance = balance + 100 WHERE account_id = 200;

   -- 트랜잭션 커밋
   COMMIT;
  

실전 예제: 은행 계좌 이체

은행 계좌 간 이체를 수행하는 시나리오를 통해 직렬화 가능 트랜잭션을 어떻게 사용하는지 살펴보겠습니다.


   -- 트랜잭션 시작
   SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

   -- 계좌 정보 조회
   SELECT balance INTO v_sender_balance FROM accounts WHERE account_id = v_sender_id FOR UPDATE;
   SELECT balance INTO v_receiver_balance FROM accounts WHERE account_id = v_receiver_id FOR UPDATE;

   -- 잔액 확인 및 이체
   IF v_sender_balance >= v_transfer_amount THEN
     UPDATE accounts SET balance = balance - v_transfer_amount WHERE account_id = v_sender_id;
     UPDATE accounts SET balance = balance + v_transfer_amount WHERE account_id = v_receiver_id;
     COMMIT;
     DBMS_OUTPUT.PUT_LINE('이체 성공');
   ELSE
     ROLLBACK;
     DBMS_OUTPUT.PUT_LINE('잔액 부족으로 이체 실패');
   END IF;
   

위 코드에서 FOR UPDATE 구문은 해당 행에 exclusive lock을 걸어 다른 트랜잭션이 동시에 해당 계좌를 수정하는 것을 방지합니다. 만약 다른 트랜잭션이 이미 해당 행에 lock을 걸었다면, 현재 트랜잭션은 lock이 해제될 때까지 대기합니다. 이는 직렬화 가능 격리 수준의 핵심적인 동작 방식입니다.

직렬화 가능 트랜잭션 사용 시 주의사항

  • 성능 저하 가능성: 직렬화 가능 격리 수준은 다른 격리 수준에 비해 동시성을 제한하므로 성능 저하를 유발할 수 있습니다.
  • 직렬화 오류(ORA-08177): 동시 트랜잭션 간의 충돌이 발생하면 ORA-08177 오류가 발생할 수 있습니다. 이 경우, 트랜잭션을 재시도해야 합니다.
  • Deadlock 발생 가능성: 트랜잭션들이 서로 다른 리소스에 대해 lock을 획득하려고 할 때 Deadlock이 발생할 수 있습니다.

직렬화 오류(ORA-08177) 처리 방법

직렬화 가능 격리 수준을 사용하는 환경에서는 ORA-08177 오류가 발생할 가능성을 염두에 두어야 합니다. 이 오류는 데이터베이스가 동시 트랜잭션들을 직렬화할 수 없을 때 발생하며, 트랜잭션을 재시도함으로써 해결할 수 있습니다.


   DECLARE
    retry_count NUMBER := 0;
    max_retries NUMBER := 3;
   BEGIN
    LOOP
      BEGIN
        -- 트랜잭션 시작
        SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

        -- SQL 문 실행
        UPDATE accounts SET balance = balance - 100 WHERE account_id = 100;
        UPDATE accounts SET balance = balance + 100 WHERE account_id = 200;

        -- 트랜잭션 커밋
        COMMIT;
        DBMS_OUTPUT.PUT_LINE('이체 성공');
        EXIT; -- 성공 시 루프 종료

      EXCEPTION
        WHEN OTHERS THEN
          IF SQLCODE = -08177 THEN
            retry_count := retry_count + 1;
            IF retry_count <= max_retries THEN
              ROLLBACK;
              DBMS_OUTPUT.PUT_LINE('직렬화 오류 발생. 재시도: ' || retry_count);
              -- 재시도 전 잠시 대기 (선택 사항)
              -- DBMS_LOCK.SLEEP(2);
            ELSE
              ROLLBACK;
              DBMS_OUTPUT.PUT_LINE('최대 재시도 횟수 초과. 이체 실패.');
              RAISE; -- 예외 다시 발생
            END IF;
          ELSE
            ROLLBACK;
            DBMS_OUTPUT.PUT_LINE('기타 오류 발생. 이체 실패.');
            RAISE; -- 예외 다시 발생
          END IF;
      END;
    END LOOP;
   END;
   /   
   

위 코드는 ORA-08177 오류가 발생했을 때 트랜잭션을 지정된 횟수만큼 재시도하는 방법을 보여줍니다. 재시도 사이에 DBMS_LOCK.SLEEP을 사용하여 잠시 대기하는 것은 트랜잭션 충돌 가능성을 줄이는 데 도움이 될 수 있습니다.

결론

직렬화 가능 트랜잭션은 Oracle 데이터베이스에서 데이터 무결성을 보장하는 강력한 도구입니다. 하지만 성능에 미치는 영향과 직렬화 오류 발생 가능성을 고려하여 신중하게 사용해야 합니다. 본 가이드에서 제시된 정보와 예시 코드를 통해 직렬화 가능 트랜잭션을 효과적으로 활용하여 안정적인 데이터베이스 애플리케이션을 개발할 수 있기를 바랍니다.

위로 스크롤