본문 바로가기

트랜잭션 격리수준

@정소민fan2025. 9. 3. 16:03

DB의 트랜잭션에는 4개의 격리수준이 있다.

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

낮은 단계의 격리수준은 동시 처리 능력을 높일 수 있지만, 데이터의 일관성 문제가 발생할 수 있다.

h2 데이터베이스를 이용해 각각의 격리수준이 어떤 일을 발생시킬 수 있는지 알아보자.

먼저, 두개의 세션으로 h2 데이터베이스 접속한 다음, 오토커밋을 꺼두자.

SET AUTOCOMMIT OFF;

더미 데이터를 만든 다음, 확인해보자.

-- 계좌 테이블 생성
CREATE TABLE ACCOUNTS (
    ID INT PRIMARY KEY,
    NAME VARCHAR(255),
    BALANCE DECIMAL(10, 2)
);

-- 초기 데이터 삽입
INSERT INTO ACCOUNTS (ID, NAME, BALANCE) VALUES (1, 'Alice', 1000.00);
INSERT INTO ACCOUNTS (ID, NAME, BALANCE) VALUES (2, 'Bob', 5000.00);

-- 데이터 삽입 후 커밋
COMMIT;

-- 데이터 확인
SELECT * FROM ACCOUNTS;

음 잘 만들어졌구만

 

들어가기에 앞서, 세션 1은 데이터를 만들고, 업데이트하는 역할이다.

세션 2는 오로지 데이터 조회만 할 것인데, 각각의 격리 수준을 시험한 뒤에는 commit 으로 트랜잭션을 끝내고 다시 시험하기 바란다.

READ UNCOMMITED

두 세션에 모두 READ UNCOMMITED 격리 수준을 설정해주자. 이 격리 수준은 커밋되지 않은 데이터도 읽어올 수 있다.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

세션 1에서 Alice의 잔액을 500으로 변경하고, 커밋하지 말자.

그리고 세션 2에서 Alice의 잔액을 조회하면 어떻게 될까?

세션 2의 결과를 보면, 세션 1에서 커밋하지도 않았는데 변경한 값이 조회되었다. 이게 바로 Dirty Read이다.

그럼 이 상태에서 세션 1에서 롤백해 버리면 어떻게 될까??

이렇게 이전 값인 1000이 조회된다. 세션 2는 존재하지도 않았던 데이터를 읽었던 것이다.

당연히 데이터 정합성에 큰 문제가 생긴다.

READ COMMITED

Read Commited 격리 수준은 커밋된 데이터만 읽어올 수 있다. 먼저 격리 수준을 다시 설정하자.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

이후 Alice의 잔액을 읽어오자.

당연히 1000으로 조회된다.

세션 1에서 Alice의 잔액을 2000으로 바꾸고, 세션 2에서 다시 조회를 실행했다.

당연히 세션 2에서는 바뀐 것으로 나타나는 게 정상이라고 생각할 수도 있지만, 같은 트랜잭션 내에서 동일한 조회문을 사용했음에도 불구하고, 다른 결과가 발생했다. 이것이 Non-Repeatable Read 이다.

REPEATABLE READ

Repeatable Read에서는 한 트랜잭션이 시작될 때, 그 당시의 스냅샷을 사용하므로 다른 세션(트랜잭션)이 데이터를 변경하고 커밋해도 동일한 결과를 보장한다. 먼저 격리 수준을 설정하자.

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

그다음, 세션 2에서 잔액이 800 이상인 잔고들을 조회하자.

여기서 세션 1에서 Alice의 잔액을 변경하고 커밋하면 어떻게 될까?

세션 1에서 잔액을 변경하고 커밋하여도, 세션 2에서 조회하였을 때는 변경이 없다. 이처럼 트랜잭션 시작 당시의 스냅샷을 여러 번  이용해 같은 조건을 조회해도 동일한 결과를 보장한다.

그렇다면 세션 1에서 새로운 데이터를 추가하면 어떻게 될까?

세션2의 트랜잭션 내에는 없었던 레코드가 나타났다. 분명히 없었던 레코드가 나타난 것이 바로 Phantom Read 이다.

그런데 분명히 트랜잭션이 시작할 때의 데이터 스냅샷을 떠서 동일한 결과를 보장한다고 했는데? 왜 이런 결과가 발생할까?

 

이는 Repeatable Read의 스냅샷이 Row 기준이기 때문이다. 이미 존재하던 Row 까지의 스냅샷은 떠 놨지만, 존재하는 Row를 넘어가서 생성되는 데이터에 범위까지는 보장하지 못한다. Phantom Read 까지 막으려면 한 단계 더 올라가야 한다.

SERIALIZABLE

격리수준을 Serializable로 설정하자.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

세션 2에서 조회를 수행하자.

이 상태에서 세션 1에서 데이터 추가를 시도하면 어떻게 될까?

어째 잘 추가된 것으로 보이지만, 아니다. 지금의 세션 1은 세션 2가 잠근 범위에 해당되는 데이터를 추가하려고 시도 중이지만, 세션 2가 commit으로 락을 놓아줄 때까지 Blocking 상태에 빠져 있다. 이 상태가 오래 지속되면, Lock Timeout이 발생할 수 있다.

 

낮은 단계의 격리수준은 자신보다 높은 단계의 격리수준에서 발생하는 문제들이 똑같이 발생할 수 있다.

격리 수준 \ 동시성 문제 Dirty Read Non-Repeatable Read Phantom Read
READ UNCOMMITTED 발생 발생 발생
READ COMMITED 방지 발생 발생
REPEATABLE READ 방지 방지 발생
SERIALIZABLE 방지 방지 방지

'DB' 카테고리의 다른 글

정규화와 반정규화  (0) 2025.09.24
Lock  (1) 2025.09.15
정소민fan
@정소민fan :: 코딩은 관성이야

코딩은 관성적으로 해야합니다 즐거운 코딩 되세요

목차