2._트랜잭션_수준_읽기_일관성

조회 수 5011 추천 수 0 2013.08.21 12:33:43
AskZZang *.33.233.130

02 트랜잭션 수준 읽기 일관성

 

(1) 트랜잭션 수준 읽기 일관성이란?

 

l  문장수준 읽기 일관성 : 쿼리가 시작된 시점을 기준으로 데이터를 일관성 있게 읽어 들이는 것

l  트랜잭션 수준 읽기 일관성 : 트랜잭션이 시작된 시점을 기준으로 일관성 있게 데이터를 읽어

 들이는 것

 

ü  오라클은 완벽한 문장수준의 읽기 일관성을 보장하지만, 트랜잭션에 대해서는 기본적으로 보장하지 않는다. 다른 DBMS도 트랜잭션 수준의 읽기 일관성은 보장하지 않는다.

 

ü  트랜잭션 수준으로 읽기 일관성을 강화하려면 트랜잭션 고립화 수준을 높여 주어야 한다..

 

(2) 트랜잭션 고립화 수준

 

ü  레벨 0 (= Read Uncommited)

-       트랜잭션에서 처리 중인, 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 것을 허용

-       Dirty Read, Non-Repeatable Read, Phantom Read 현상 발생

-       Oracle은 이 레벨을 지원하지 않음

 

ü  레벨 1 (= Read Commited)

-       Dirty Read 방지 : 트랜잭션이 커밋되어 확정된 데이터만 읽는 것을 허용

-       대부분의 DBMS가 기본모드로 채택하고 있는 일관성 모드

-       Non-Repeatable Read, Phantom Read 현상은 여전히 발생

-       DB2, SQL Server, Sybase의 경우 읽기 공유 Lock을 이용해서 구현. 하나의 레코드를 읽을 때 Lock을 설정하고 해당 레코드를 빠져 나가는 순간 Lock 해제

-       Oracle Lock을 사용하지 않고 쿼리시작 시점의 Undo 데이터를 제공하는 방식으로 구현

 

ü  레벨 2 (= Repeatable Read)

-       선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신하거나 삭제하는 것을 불허함으로써 같은 데이터를 두 번 쿼리했을 때 일관성 있는 결과를 리턴

-       Phantom Read 현상은 여전히 발생

-       DB2, SQL Server의 경우 트랜잭션 고립화 수준을 Repeatable Read로 변경하면 읽은 데이터에 걸린 공유 Lock을 커밋할 때까지 유지하는 방식으로 구현

-       Oracle은 이 레벨을 명시적으로 지원하지 않지만 for update절을 이용해 구현가능.

-       SQL Server 등에서도 for update절을 사용할 수 있지만 커서를 명시적으로 선언할 때만 사용 가능함.

 

ü  레벨 3 (= Serializable Read)

-       선행 트랜잭션이 읽은 데이터를 후행 트랜잭션이 갱신하거나 삭제하지 못할 뿐만 아니라 중간에 새로운 레코드를 삽입하는 것도 막아줌

-       완벽한 읽기 일관성 모드를 제공

 

 

DBMS에서 제공하는 트랜잭션 고립화 수준 조정기능을 이용해 트랜잭션 레벨 읽기 일관성을 확보하려면

Set transaction isolation level serializable;

 

낮은 단계의 트랜잭션 고립화 수준을 사용할 때 발생하는 세가지 현상

l  Dirty Read (= Uncommitted Dependency)

l  Non-Repeatable Read (=Inconsistent Analysis)

l  Phantom Read

 

 

(3) Dirty Read (= Uncommitted Dependency)

 

ü  아직 커밋되지 않은 수정 중인 데이터를 다른 트랜잭션에서 읽을 수 있도록 허용할 때 발생

 

ü  대부분 DBMS가 기본 트랜잭션 고립화 수준을 레벨 1(=Read Committed)로 설정하고 있기 때문에 Dirty Read 현상은 발생하지 않는다. , 커밋된 데이터만 읽을 수 있도록 허용한다.

 

ü  SQL Server, Sybase 등은 이를 구현하기 위해 데이터를 읽을 때 공유 Lock을 사용

 

ü  갱신 중인 레코드에는 배타적 Lock이 걸리는데, 이는 공유 Lock과 호환되지 않으므로 갱신 중인 레코드를 읽지 못한다.

 

ü  오라클은 다중 버전 읽기 일관성 모델을 채택함으로써 Lock을 사용하지 않고도 Dirty Read를 피해 일관성 있는 데이터 읽기가 가능하므로 레벨 0 수준으로 트랜잭션 고립화 수준을 낮추는 방법을 아예 제공하지 않고 있다.

 

 

(4) Non-Repeatable Read (= Inconsistent Analysis)

 

한 트랜잭션 내에서 같은 쿼리를 두 번 수행할 때, 그 사이에 다른 트랜잭션이 값을 수정 또는 삭제함으로써 두 쿼리의 결과가 상이하게 나타나는 비일관성(Inconsistency)이 발생하는 것

 

< Non-Repeatable Read 때문에 데이터가 불일치 상태에 놓이게 되는 사례 >

 

 a.JPG

 

 

1.     T1 시점에 123번 고객의 당월주문금액은 40,000원이였다고 가정

2.     1번 쿼리를 통해 :amt 변수에 40000이 저장

3.     두 트랜잭션이 수행을 종료하고 나면, TX2 트랜잭션에 의해 고객의 당월주문금액이 60,000원으로 변경

4.     T4 시점에 고객등급이 ‘B’로 하향 조정되는 결과

5.     Non-Repeatable Read 현상 때문에 TX2 트랜잭션의 등급 조정이 상실되는 Lost Update가 발생

 

Non-Repeatable Read 현상을 방지하려면 TX1 1번 문장을 수행할 때 for update절을 추가해 줘야 함.

그러면 123번 고객 레코드에 Lock이 걸리므로 TX2 트랜잭션은 블로킹 됐다가 TX1이 커밋하는 t5 시점 이후에 진행을 계속한다.

 

< Non-Repeatable Read의 또 다른 사례 >

 

b.JPG

 

1.     T1 시점에 123번 계좌번호의 잔고는 55,000원이었다고 가정

2.     1번 쿼리를 통해 자신의 계좌에 55,000원이 남아 있음을 확인

3.     T4 시점에 10,000원을 인출하려고 시도했으나 TX2 트랜잭션에 의해 이 계좌의 잔고가 5,000원으로 변경

4.     잔고가 충분한 것을 확인하고 인출을 시도했음에도 불구하고 잔고가 부족하다는 메시지를 받음

5.     TX1 트랜잭션이 1번 쿼리에서 읽은 값을 이용해 2번에서 값을 갱신하는데 사용하지는 않았으므로 데이터가 잘못 갱신되는 문제는 발생하지 않음

 

 

(5) Phantom Read

 

ü  한 트랜잭션 안에서 일정범위의 레코드들을 두 번 이상 읽을 때, 첫 번째 쿼리에서 없던 유령(Phantom) 레코드가 두 번째 쿼리에서 나타나는 현상

 

ü  트랜잭션 도중에 새로운 레코드가 삽입되는 것을 허용하기 때문에 나타나는 현상

 

< Phantom Read 현상에 의해 데이터가 불일치 상태에 놓이게 되는 사례 >

 

c.JPG

 

1.     Tx1 트랜잭션이 지역별고객과 연령대별고객을 연속해서 집계

2.     새로운 고객이 TX2 트랜잭션에 의해 등록

3.     그 결과, 지역별고객과 연령대별고객 두 집계 테이블을 통해 총고객수를 조회하면 서로 결과 값이 다른 불일치 상태

 

Phantom Read 현상을 방지하려면 TX11번 문장을 수행하기 전에 트랜잭션 고립화 수준을 레벨 3으로 올려주어야 한다.

 

Set transaction isolation level serializable;

 

고립화 수준을 레벨 3으로 높이면 SQL Server에서는 Lock을 통해 t2 시점에 새로운 고객이 추가되는 것을 막아 버린다.

고립화 수준을 높이면 데이터 일관성은 확보 되지만 동시성이 현격히 저하되는 결과를 초래

 

오라클은 Lock을 전혀 사용하지 않은 상태에서 1번과 2번 쿼리 모두 SCN 확인 과정을 통해 t1 시점에 존재했던 고객만을 대상으로 집계를 수행하므로 동시성을 저하시키지 않으면서 일관성을 유지

 

< Phantom Read에 의해 데이터 일관성이 훼손되는 또 다른 사례 >

 

d.JPG

 

1.     Tx1 트랜잭션이 로그 테이블을 백업하고 전체 로그 데이터를 지운 후에 커밋을 수행

2.     TX1 1~3 세 개 명령을 하나의 트랜잭션으로 일관성 있게 처리하려면 t1 시점에 존재했던 로그 데이터만 백업하고 지워야 함

3.     기본 트랜잭션 고립화 수준(레벨 1)에서는 TX2 트랜잭션에 의해 t2~t3 시점에 추가된 로그 데이터까지 TX1 트랜잭션에 의해 삭제

 

TX1 1번 문장을 수행하기 전에 트랜잭션 고립화 수준을 레벨 3으로 올려주면 이를 방지할 수 있다.

-       SQL Server에서는 Lock을 통해 t2 시점에 새로운 로그가 추가되는 것을 막음

-       오라클은 TX1 트랜잭션이 t4 시점에 delete 문장을 수행하더라도 t1 시점에 존재했던 레코드만 찾아서 지우기 때문에 Lock을 사용하지 않고도 TX2 트랜잭션에 의해 새로 추가된 로그 데이터를 안전하게 보호

 

e.JPG

 

트랜잭션 고립화 수준을 높이면 일관성(Consistency)은 향상 되지만, 더 넓은 범위의 Lock을 더 오랫동안 유지하는 방식을 사용하므로 동시성(Concurrency)은 저하된다.

 

오라클은 트랜잭션 고립화 수준을 높이더라도 Lock을 사용하지 않으므로 동시성이 저하되지는 않는다.

 

 

< SERIALIZABLE_ABORTS >

 

트랜잭션 고립화 수준을 구현하기 위해 오라클을 제외한 다른 DBMS Lock을 이용하지만, 오라클은 for update절을 사용하지 않는 한 절대 select문에 Lock을 사용하지 않는다.

 

ð  레벨을 상향 조정하더라도 이전과 동일한 동싱성을 보장한다.

ð  높은 일관성 수준에서도 높은 동시성을 제공할 수 있는 이유는, Undo 데이터를 활용하기 때문

 

트랜잭션 레벨에서 일관성 있는 데이터를 제공할 수 없는 상황이 발생하면 아래와 같은 에러를 던진다.

 

 

5번 고객이 최초 잔고가 1,000원인 상태에서 만약 t1 시점에 트랜잭션 고립화 수준을 조정하지 않고 트랜잭션을 진행한다면 두 트랜잭션이 완료된 후 최종 잔고는 2,500원이 될 것이다.

 

위처럼 트랜잭션 고립화 수준을 Serializable로 바꾸고 나서 트랜잭션을 수행하면

ð  트랜잭션이 시작한 시점, t1 시점을 기준으로 값을 읽어야 한다.

ð  T2~T3 시점에 값이 변경되더라도 T4 시점의 select 문은 Undo 데이터를 이용해서 T1 시점의 데이터를 재현해 내면 되므로 문제가 없다.

ð  하지만 t5 시점의 update문에서 5번 고객의 잔고가 이미 2,000원으로 변경된 상태에서 트랜잭션 A t1 시점의 값을 기준으로 update를 수행하면 갱신이 오나료된 시점에는 잔고가 1,500원이 된다. Lost Update가 발생하는 것이다.

ð  오라클은 이를 방지하기 위해 ORA-08177 에러를 던진다.

ð  높은 수준의 동시성과 읽기 일관성을 유지하는 대신 다른 DBMS에 없는 Snapshot too old(ORA-01555)를 얻게 된 것과 같은 현상

ð  V$SQL에서 serializable_aborts > 0 인 레코드를 조회하면 이 에러를 만났던 SQL들을 확인