반복 가능한 읽기 (Repeatable Reads) 보장

격리 수준과 동시성 제어

데이터베이스 시스템에서 격리 수준(Isolation Level)은 여러 트랜잭션이 동시에 데이터에 접근할 때, 각 트랜잭션이 서로에게 미치는 영향을 제어하는 설정을 의미합니다. 격리 수준을 높게 설정할수록 데이터의 일관성은 보장되지만, 동시성이 낮아져 성능 저하를 초래할 수 있습니다. 따라서 애플리케이션의 요구사항에 맞는 적절한 격리 수준을 선택하는 것이 중요합니다.

ANSI SQL 표준 격리 수준

ANSI SQL 표준은 4가지 격리 수준을 정의하고 있으며, 각 수준은 서로 다른 유형의 동시성 문제를 허용합니다. 각 격리 수준별로 발생 가능한 현상은 다음과 같습니다.

  • READ UNCOMMITTED (커밋되지 않은 읽기): 가장 낮은 격리 수준으로, 다른 트랜잭션에서 아직 커밋하지 않은 변경 사항을 읽을 수 있습니다. Dirty Reads, Non-Repeatable Reads, Phantom Reads 모두 발생 가능합니다.
  • READ COMMITTED (커밋된 읽기): 오라클의 기본 격리 수준이며, 다른 트랜잭션에서 커밋된 변경 사항만 읽을 수 있습니다. Dirty Reads는 방지되지만 Non-Repeatable Reads, Phantom Reads는 발생 가능합니다.
  • REPEATABLE READ (반복 가능한 읽기): 트랜잭션이 시작된 후에는 다른 트랜잭션이 해당 트랜잭션에서 읽은 데이터를 변경할 수 없습니다. Non-Repeatable Reads는 방지되지만 Phantom Reads는 발생 가능합니다.
  • SERIALIZABLE (직렬화 가능): 가장 높은 격리 수준으로, 모든 동시성 문제를 방지합니다. 트랜잭션은 마치 다른 모든 트랜잭션이 완료된 후에 실행되는 것처럼 동작합니다.

오라클에서 REPEATABLE READ 구현

오라클 데이터베이스는 ANSI SQL 표준의 REPEATABLE READ 격리 수준을 직접적으로 지원하지 않습니다. 오라클은 기본적으로 READ COMMITTED 격리 수준을 사용하며, 더 높은 수준의 격리를 위해 SERIALIZABLE 격리 수준을 제공합니다. 하지만 READ COMMITTED 격리 수준에서도 필요한 경우 어플리케이션 로직과 다양한 기능을 통해 반복 가능한 읽기(Repeatable Reads)와 유사한 수준의 데이터 일관성을 확보할 수 있습니다.

오라클 MVCC (Multi-Version Concurrency Control)

오라클은 MVCC 메커니즘을 사용하여 READ COMMITTED 격리 수준에서 데이터의 일관성을 유지합니다. MVCC는 데이터를 읽는 시점에 해당 데이터의 스냅샷을 제공하여, 다른 트랜잭션의 변경 사항으로부터 읽기 트랜잭션을 보호합니다. 하지만 기본적으로 다른 트랜잭션에서 커밋된 내용이 이후 읽기에 반영되므로, 완전히 반복 가능한 읽기를 보장하지는 않습니다.

Explicit Locking을 이용한 반복 가능한 읽기 구현

반복 가능한 읽기를 더 엄격하게 구현하려면, 명시적 잠금(Explicit Locking)을 사용할 수 있습니다. 특정 테이블에 대한 잠금을 획득하면, 다른 트랜잭션은 해당 테이블의 데이터를 변경할 수 없으므로, 트랜잭션이 시작된 이후 데이터가 변경되지 않음을 보장할 수 있습니다.

LOCK TABLE 구문

LOCK TABLE 구문을 사용하면 특정 테이블에 대한 잠금을 획득할 수 있습니다. MODE 옵션을 통해 획득할 잠금의 유형을 지정할 수 있으며, NOWAIT 옵션을 사용하면 잠금을 즉시 획득할 수 없는 경우 오류를 반환합니다.

“`sql LOCK TABLE employees IN SHARE MODE NOWAIT; “`

위 예제는 employees 테이블에 대해 공유 잠금(SHARE MODE)을 획득하는 구문입니다. 공유 잠금을 획득하면 다른 트랜잭션은 해당 테이블에 대해 공유 잠금을 획득할 수 있지만, 배타적 잠금(EXCLUSIVE MODE)은 획득할 수 없습니다.

“`sql LOCK TABLE departments IN EXCLUSIVE MODE NOWAIT; “`

위 예제는 departments 테이블에 대해 배타적 잠금(EXCLUSIVE MODE)을 획득하는 구문입니다. 배타적 잠금을 획득하면 다른 트랜잭션은 해당 테이블에 대해 어떤 유형의 잠금도 획득할 수 없습니다.

사용자 정의 PL/SQL 블록을 이용한 구현 예시

아래 코드는 커밋 또는 롤백 전까지 EMPLOYEES 테이블에 대한 배타적 락을 유지하는 PL/SQL 블록의 예시입니다.

“`sql DECLARE — 예외 처리 루틴: 락을 해제하고 예외를 다시 발생시킴 PROCEDURE release_lock_and_raise_exception (p_error_message VARCHAR2) IS BEGIN — 테이블 락 해제 EXECUTE IMMEDIATE ‘LOCK TABLE employees IN EXCLUSIVE MODE NOWAIT’; — 예외 발생시키기 RAISE_APPLICATION_ERROR(-20001, p_error_message); END; BEGIN — EMPLOYEES 테이블에 배타적 락 획득 시도 EXECUTE IMMEDIATE ‘LOCK TABLE employees IN EXCLUSIVE MODE NOWAIT’; — 테이블에서 데이터를 선택하는 몇몇 작업 수행 — 에러가 발생하는 경우, 락을 해제하고 예외를 발생시키세요 — 데이터가 변경되는 경우 락을 유지하고 커밋하세요 — 성공하면 작업 커밋 COMMIT; EXCEPTION WHEN OTHERS THEN — 오류 발생 시, 테이블 락을 해제하고 예외를 발생시키는 예외 핸들러 release_lock_and_raise_exception(‘트랜잭션 실패: ‘ || SQLERRM); END; / “`

참고로, 위 PL/SQL 블록은 Oracle 버전 12c 이상에서 제대로 실행됩니다. 이전 버전의 경우 테이블 레코드를 잠그는 다른 방법을 찾아봐야 합니다.

SERIALIZABLE 격리 수준

SERIALIZABLE 격리 수준은 가장 높은 수준의 데이터 일관성을 제공하지만, 동시성을 제한하여 성능 저하를 초래할 수 있습니다. 오라클에서 SERIALIZABLE 격리 수준은 다음과 같은 특징을 가집니다.

  • Serializable: 트랜잭션은 다른 트랜잭션의 변경 사항을 볼 수 없으며, 다른 트랜잭션이 해당 트랜잭션에서 읽은 데이터를 변경하는 것을 방지합니다.
  • Read Consistency: 트랜잭션은 항상 시작 시점의 데이터와 일관된 데이터를 읽습니다.
  • Phantom Reads 방지: 트랜잭션이 시작된 이후에는 다른 트랜잭션이 새로운 데이터를 삽입하여 해당 트랜잭션의 결과에 영향을 미치는 것을 방지합니다.

SERIALIZABLE 격리 수준 설정

SET TRANSACTION 구문을 사용하여 트랜잭션의 격리 수준을 SERIALIZABLE로 설정할 수 있습니다.

“`sql SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; “`

SERIALIZABLE 격리 수준 사용 시 주의사항

SERIALIZABLE 격리 수준은 동시성을 제한하므로, 애플리케이션의 성능에 영향을 미칠 수 있습니다. 또한 SERIALIZABLE 트랜잭션은 다른 트랜잭션과 충돌할 경우 오류를 반환할 수 있습니다. 따라서 SERIALIZABLE 격리 수준은 데이터 일관성이 매우 중요한 트랜잭션에만 신중하게 사용해야 합니다.

정리

오라클 데이터베이스는 ANSI SQL 표준의 REPEATABLE READ 격리 수준을 직접적으로 지원하지 않지만, MVCC 메커니즘, 명시적 잠금, SERIALIZABLE 격리 수준 등의 다양한 기능을 통해 반복 가능한 읽기와 유사한 수준의 데이터 일관성을 확보할 수 있습니다. 애플리케이션의 요구사항과 성능 목표를 고려하여 적절한 방법을 선택하는 것이 중요합니다.

위로 스크롤