변수의 기록

(DB)트랜잭션 이상 현상 , Isolation Level 정리 (SQL-92 기준) 본문

CS지식/데이터베이스 (Database)

(DB)트랜잭션 이상 현상 , Isolation Level 정리 (SQL-92 기준)

불광동 물주먹 2025. 5. 20. 01:07

1.  트랜잭션은 동시에 실행된다

대부분의 데이터베이스 시스템은 성능을 위해 여러 트랜잭션을 동시에 병렬 실행시킨다.
그런데 문제는, 이 과정에서 트랜잭션들이 서로 영향을 주고받을 수 있다는 점이다.

그 영향은 때때로 심각하다.
읽어서는 안 될 값을 읽거나, 중간에 사라지는 값을 읽고 계산하거나,
더 나쁘면 트랜잭션이 업데이트한 결과가 통째로 덮어써지며 날아간다.

 


2. 동시에 실행될 때 발생하는 대표적인 이상 현상들


 

이상 현상 설명
Dirty Read 아직 commit되지 않은 트랜잭션이 작성한 값을 읽음
Non-repeatable Read 같은 쿼리를 두 번 실행했는데 결과가 다름
Phantom Read WHERE 조건은 같지만, 행의 개수가 달라지는 현상
 

→ 이런 현상들은 트랜잭션 격리 수준(Isolation Level)을 조정함으로써 제어할 수 있다.

 

& 예제!

 2-1 .Dirty Read (더티 리드)

정의:
트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 값을 읽는 경우

-- T1: 값 변경 (커밋 안 함)
BEGIN;
UPDATE accounts SET balance = 0 WHERE id = 1;

-- T2: T1이 커밋도 안 한 데이터를 읽음
BEGIN;
SELECT balance FROM accounts WHERE id = 1;  -- 0

-- T1 롤백
ROLLBACK;

-- T2는 rollback된 값(0)을 읽었음 → 잘못된 데이터 사용
 
→ 잘못된 데이터 사용

❌ 방지되지 않으면 데이터 정합성 위반


 2-2 . Non-Repeatable Read (반복 불가능한 읽기)

정의:
같은 데이터를 두 번 읽었을 때 값이 달라지는 현상

-- T1: 첫 번째 읽기
BEGIN;
SELECT balance FROM accounts WHERE id = 1;  -- 100

-- T2: 값 변경 및 커밋
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;
COMMIT;

-- T1: 두 번째 읽기
SELECT balance FROM accounts WHERE id = 1;  -- 200

-- ❗ 두 번 읽었는데 결과가 달라짐

✅ Repeatable Read 이상에서 방지 가능


 2-3. Phantom Read (팬텀 리드)

정의:
같은 조건의 SELECT에서 새로운 행이 생겨 결과가 달라지는 현상

-- T1: 조건 검색
BEGIN;
SELECT * FROM orders WHERE amount > 100;  -- 2건 반환

-- T2: 새로운 데이터 삽입
BEGIN;
INSERT INTO orders (id, amount) VALUES (1004, 200);
COMMIT;

-- T1: 다시 검색
SELECT * FROM orders WHERE amount > 100;  -- 3건 반환

-- ❗ 같은 조건인데 결과에 "팬텀 행"이 생김

✅ Serializable 수준에서만 방지 가능

 

 

 

추가 이상 현상들!!

( 아래는 SQL-92 표준에 포함되어 있지 않음. )

Lost Update 동시에 업데이트하면서 이전 결과가 사라지는 현상
Dirty Write 아직 commit되지 않은 값을 다른 트랜잭션이 덮어씀
Read Skew / Write Skew 연산 중간 값들이 일관되지 않아 잘못된 결과를 유도함

 

 2-4. Dirty Write (더티 라이트)

정의:
커밋되지 않은 데이터를 다른 트랜잭션이 덮어써버리는 경우

-- T1: A에 값 설정
BEGIN;
UPDATE config SET value = 'v1' WHERE key = 'timeout';

-- T2: 같은 row를 다시 덮어씀 (T1은 아직 커밋 안 함)
BEGIN;
UPDATE config SET value = 'v2' WHERE key = 'timeout';

-- T1 롤백
ROLLBACK;

-- ❗ 이제 A의 값은 'v2' → T2는 T1의 임시 데이터를 덮어씀 → 일관성 깨짐

✅ 대부분의 DBMS는 기본적으로 방지 (락 기반)


 2-5. Lost Update (갱신 손실)

정의:
두 트랜잭션이 동시에 같은 데이터를 수정, 나중에 커밋된 값이 먼저 작업한 내용을 덮어써서 유실

-- T1: A를 읽고 계산
BEGIN;
SELECT stock FROM product WHERE id = 1;  -- 100
UPDATE product SET stock = 90 WHERE id = 1;

-- T2: 같은 시점에 읽고 계산
BEGIN;
SELECT stock FROM product WHERE id = 1;  -- 100
UPDATE product SET stock = 95 WHERE id = 1;

-- T1, T2 둘 다 COMMIT

-- ❗ 결과: stock = 95 → T1의 작업이 유실됨 (Lost Update)

✅ Serializable 수준이거나 명시적 락 필요


 2-6. Read Skew

정의:
서로 연관된 데이터를 읽었지만, 읽은 시점이 다른 타이밍이어서 논리적으로 모순된 결과를 초래

-- T1: 잔액 검증
BEGIN;
SELECT balance FROM account_A;  -- 100
SELECT balance FROM account_B;  -- 100
-- 합이 200 → 이상 없음

-- T2: A → B로 이체
BEGIN;
UPDATE account_A SET balance = 0;
UPDATE account_B SET balance = 200;
COMMIT;

-- T1: 합은 여전히 200으로 보이지만, A와 B의 상태는 불일치된 상태를 반영함
-- ❗ 스냅샷 시점이 어긋나 일관성 깨짐 (skew 발생)

✅ Snapshot Isolation 또는 Serializable로 해결


 2-7. Write Skew

정의:
트랜잭션들이 각각 제약조건을 만족하면서 동시에 쓰기 작업을 하여
결과적으로 비즈니스 제약을 위반

-- 병원 당직 시스템 (의사는 둘 중 하나는 반드시 근무)
-- T1: A 의사 휴무 신청
BEGIN;
SELECT * FROM schedule WHERE doctor = 'A';  -- B는 근무 중
DELETE FROM schedule WHERE doctor = 'A';

-- T2: B 의사도 휴무 신청
BEGIN;
SELECT * FROM schedule WHERE doctor = 'B';  -- A는 근무 중
DELETE FROM schedule WHERE doctor = 'B';

-- T1, T2 둘 다 COMMIT

-- ❗ 결과: A, B 모두 휴무 → 당직 의사 없음 → 제약 위반 (Write Skew)

✅ Serializable만이 이런 병렬 비즈니스 제약을 확실히 방지

 

 

 


3. 완벽하게 막을 수는 있지만… 비용이 따른다

모든 이상 현상을 방지하려면 트랜잭션을 철저히 격리시키면 된다.
예를 들어 트랜잭션을 한 번에 하나씩만 실행한다면 모든 문제가 사라진다.

그러나 그렇게 하면 성능이 심각하게 저하되고,
DBMS의 처리량(throughput)이 줄어 대규모 시스템에서는 현실적으로 불가능하다.

그래서 SQL 표준은 일부 현상은 허용하면서도 선택적으로 격리 수준을 조절할 수 있는 네 가지 Isolation Level을 정의했다.


4. SQL 표준 Isolation Level (SQL-92 기준)

 

Level Dirty Read Non-repeatable Read Phantom Read 설명
Read Uncommitted 허용 허용 허용 가장 낮은 격리 수준. Dirty Read 허용
Read Committed 방지 허용 허용 오라클의 기본값. Dirty Read는 방지되지만, 반복 읽기 불가
Repeatable Read 방지 방지 허용 같은 쿼리를 두 번 실행해도 결과는 같음
Serializable 방지 방지 방지 트랜잭션이 완전히 직렬화된 것처럼 보이게 함
 

상위 수준일수록 데이터 정합성은 높아지지만, 동시성은 낮아진다.


5. Oracle은 Isolation Level을 다르게 구현한다

Oracle은 ANSI SQL-92를 따르되, 자체적으로 내부 동작 방식이 다르다.
그 이유는 Oracle이 멀티 버전 컨커런시 제어(MVCC) 기반의
Snapshot Isolation 방식을 채택하기 때문이다.

Oracle의 특성 요약

 

Isolation Level Dirty Read Non-repeatable Read Phantom Read 구현 방식
READ UNCOMMITTED ❌ 불허함 허용 허용 Oracle은 dirty read 자체를 막음
READ COMMITTED (기본) ✅ 허용 ✅ 허용 커밋된 값만 보되, 읽을 때마다 최신 커밋 값 기준
SERIALIZABLE ❌ 방지 ❌ 방지 Snapshot 시점의 데이터를 격리
 

Oracle에서는 REPEATABLE READ가 따로 존재하지 않으며,
SERIALIZABLE만 명시적으로 설정 가능하다.


6. Snapshot Isolation in Oracle

Oracle은 READ COMMITTED 수준에서도 일반 DB와 달리 MVCC 기반으로 동작하여,
읽기 시점에 해당하는 Snapshot View만을 보여준다.

특징

  • 트랜잭션이 시작되면, 그 시점까지 커밋된 데이터만 읽을 수 있음
  • 이후 커밋된 변경 사항은 읽기 대상이 되지 않음 (읽기 일관성 유지)
  • 동시에 다른 트랜잭션이 데이터를 바꿔도, 이미지 사본을 통해 격리됨

이것이 바로 Snapshot Isolation이다.


7. 그럼 Serializable은 어떻게 다를까?

Oracle에서 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE로 설정하면
아래와 같은 특징이 적용된다:

  • 트랜잭션이 시작될 때 전체 DB의 Snapshot을 고정
  • 이후 해당 스냅샷 기준으로만 읽고 쓸 수 있음
  • Phantom Read까지 방지됨 (기존 결과 외 새로운 행도 안 보임)
  • 하지만 동시에 같은 데이터를 수정하려 하면 Serialization 오류 발생

→ 따라서 무조건 안전하지만 충돌 위험과 처리량 저하가 크다


8. 중요 포인트

  • 일관성이 중요한 시스템 (금융, 재고, 회계 등)은 SERIALIZABLE 또는 Serializable+락
  • 실시간 응답성과 처리량이 중요한 시스템 (광고, 로그, 분석 등)은 READ COMMITTED 또는 MVCC 기반의 비차단 읽기가 더 적합
  • Oracle은 기본적으로 cascadeless + strict 수준의 안전성을 MVCC로 구현하고 있음

9. 관련 이상 현상들 보충 설명

현상 설명
Dirty Write 두 트랜잭션이 commit 전 데이터를 서로 덮어씀. 매우 위험하여 대부분 DB에서 방지
Lost Update 마지막에 write한 트랜잭션이 이전 트랜잭션의 변경 사항을 덮어써 손실 발생
Read Skew 하나의 트랜잭션에서 두 값(A, B)을 읽었는데, 각 값은 서로 다른 시간대 기준일 수 있음
Write Skew 두 트랜잭션이 서로 다른 조건을 만족한 후 동시에 쓰기 → 비즈니스 로직 위반 가능성
 

 


10. 마무리

트랜잭션 격리는 단순히 "동시에 실행돼도 괜찮은가?"의 문제가 아니다.
무엇을 얼마나 허용할 것인지,
어떤 현상을 막고 어떤 비용을 감수할 것인지에 대한 전략적 선택이다.

Oracle은 MVCC + Snapshot Isolation을 기반으로
높은 일관성과 성능 사이에서 실용적인 절충안을 제공하고 있으며,
실무에서는 이를 비즈니스 도메인별로 신중히 선택해야 한다.

 

 

 

 

*내용 참고 : 유튜브 쉬운코드