h1.6.문장수준 읽기 일관성
단일 SQL문이 수행되는 도중에, 또는 트랜잭션 내에서 일련의 SQL문이 차례로 수행되는 도중에 다른 트랜잭션에 의해 데이터가 변경,추가,삭제된다면 일관성 없는 결과집
합을 리턴하거나 값을 잘못 갱신하는 문제가 발생할 수 있다. 이런 현상을 방지하고 읽기일관성 (Read Consistency)을 보장하기 위해 DBMS마다 나름대로의 장치들을 마련하고 있다

h3.(1) 문장수준 읽기 일관성이란?
문장수준 읽기 일관성은, 단일 SQL문이 수행되는 도중에 다른 트랜잭션에 의해 데이터의 추가,변경,삭제가 발생하더라도 일관성 있는 결과집합을 리턴히는 것을 말한다.
예를 들어,변경이 진행 중인 값, 즉 아직 커빗되지 않은 값을 다른 트랜잭션이 읽도록 Dirty Read를 허용한다면 읽기 일관성이 보장되지 않는다.
오라클을 제외한 다른DBMS는 모두 로우 Lock을 사용해 Dirty Read를 방지한다. 즉, 읽기 작업에 대해 Shared Lock을 사용함으로써 Exclusive Lock이 걸린 로우를 읽지 못하도록 한다.

h5.트랜잭션 TX 이 계좌 테이블을 읽어 모든 계좌의 잔고 총합계를 구하는 쿼리를 수행

<사례1>
위 쿼리가 진행되는 동안 다른 트랜잭션 TX에서 아래 insert문을 통해 새로운 계좌의 잔고 데이터를 추가하고, 커밋하였다.


TX2> insert into 계좌(계좌번호, 잔고) values ( 11, 1000 )
TX2> corrmit ;

만약 새 레코드가 맨 뒤쪽에 추가되면 잔고 총합계에 포함되겠지만 이미 읽고 지나간 위치에 삽입되면 총합계에서 누락된다.
레코드가 입력되는 위치에 따라 총합계가 달라진다면 읽기 일관성이 없는 것이다.

<사례2>
또 다른 예로서, TX1에서 잔고 총합을 구하는 쿼리가 진행 중일 때 다른 트랜잭션 TX2가 TX1에서 이미 읽고 지나간 레코드에서 잔고를 차감해 앞으로 읽을 레코드에 잔고를
더하거나 반대로,TX1이 앞으로 입을 레코드에서 잔고를 차감해 이미 읽고 지나간 레코드에 잔고를 더한다면 잔고 총합계가 다르게 구해질 수 있다.
이런 현상을 방지하려면 트랜잭션 고립화 수준을 올리거나 테이블 Lock을 사용해야 한다.(2장) 하지만 오라클에서는 절대 이런 현상이 발생하지 않는다.

다른 DBMS에서는 실제 위와 같은 현상이 발생할 수 있다는 뜻일까 답은 "그렇다" 이다. insert히는 〈사례1>은 Sybase, SQL Server 등에서 쉽게 재현 가능하므로 생략하기로 하고, update하는 〈사례 2>를 같이 시율레이션해 보자.


TX2> update 계좌 set 잔고 = 잔고 + 100 where 계좌번호 =  7   -- ①
TX2> update 계좌 set 잔고 = 잔고 - 100 where 계좌번호 =  3   -- ②
TX2> conmit;


1. TX1 : 2번 계좌까지 읽는다. (sum = 2,000)
2. TX2 : ①번 update를 실행한다. (7번 계좌 잔고 = 1100 , uncommitted 상태) 
3. TX1 : 6번 계좌까지 읽어 내려간다. (sum = 6 ,000)
4. TX2 : ②번 update를 실행하고, 커빗한다.
        (3 번 계좌 진고 = 900, 7 번 계좌 잔고 = 1100, committed 상태)
5. TX1 :10번 계좌까지 읽어 내려간다.7번 계좌 잔고를 1,100으로 바꾼 TX(2 트랜잭션이 커빗되었으므로 이 값을 읽어 
        잔고 총합은 10.100으로 계산된다. 계좌 잔고 총합계는, TX2 트랜잭션이 진행되기 전에도 10,000 이었고, 
        TX2 트랜잭션이 완료된후에도 10,000이므로 TX1은 일관성 없게 잔고 총합계를 구한 것이 된다.

말도 안 된다고 생각할지 모르겠지만 Sybase나 SQL Server에서 태스트해 보면 실제로 위와 같은 현상이 발생하는 것을 눈으로 확인할 수 있다.

테이블 레벨 Lock을 통한 읽기 일관성 확보


위와 같은 비일관성 읽기 문제를 해결하기 위한 일반적인 해법은 트랜잭션 고립화 수준{다음장서 설명함)을 상향 조정히는 것이다. 
SQL Server, Sybase 등은 Lock을 사용해 읽기 일관성을 구현하는데, 기본 트랜잭션 고립화 수준{Level 1, Read Committed)에서는 값을 읽는 순간에만
Shared Lock을 걸었다가 다음 레코드로 이동할 때 Lock을 해제한다. Shared Lock이 해제되기 때문에 이미 읽고 지나간 레묘드를 다른 트랜잭션이 변경할 수 있게 되고,
이때문에 문장수준 읽기 일관성이 보장되지 않는 문제가 생긴다. 트랜잭션 고립화 수준을 레벨2(=Repeatable Read)로 조정하면 TX1쿼리가 진행되는 동안 
읽은 레코드는 Shared Lock이 계속유지되며 심지어 쿼리가 끝나고 다음 쿼리가 진행되는 동안에도 Lock을 풀지 않는다. 커빗또는 롤백을 통해 트랜잭션이 완료될 때 
비로소 Lock이 해제되므로 더 높은 수준의 읽기 일관성이보장된다.
하지만 트랜잭션 고립화 수준을 상향 조정하면 Lock이 발생하는 범위가 넓어지고 더 오래유지되기 때문에 동시성 저하를 초래함은 물론이고 교착상태(Dead Lock가 발생할 가능성이 높
아진다. 바로 위 사례가 대표적인 케이스다.

오라클 데이터베이스를 사용하고 있다면 위와 같은 상황을 우려하지 않아도 된다. 오라클은 높은 수준의 동시성을 유지하면서도 완벽한 문장수준 읽기 일관성을 보장하기 때문이다.

h3.(2) Consistent 모드 블록 읽기

  • 그림 1-14에 도식화한 것처럼 오라클은 쿼리가 시작된 시점을 기준으로 데이터를 읽어 들인다. 쿼리가 시작되기 전에 이미 커빗된 데이터만 읽고 쿼리 시작 이후에 커빗된 변경사항은 읽어들이지 않는다(current mode).
  • 변경이 발생한 블록을 읽을 때는 현재의 current 블록으로부터 CR 블록을 생성해서 쿼리가 시작된 시점으로 되돌린 후 그것을 읽는다.
  • Current 블록은 디스크로부터 읽혀진 후 시용자의 갱신사항이 반영된 최종 상태의 원본 블록을 말하며 CR 블록은 Current 블록에 대한 복사본이다.
  • CR 블록은 여러 버전이 존재할 수 있지만 Current 블록은 오직 한 개뿐이다.
  • 이처럼 Current 블록을 여러 개의 CR Copy 블록으로 복사해 읽기 일관성을 지원하는 오라클만의 독특한 메커니즘을 다중 버전읽기 일관성 모댈(Multi-Version Read Consistency Moddel)' 이 라고 한다.

RAC 환경에서의 Current 블록


단일 인스턴스 환경이라면 캐싱된 Current 블록은 오직 한 개뿐이지만 RAC 환경이라면 Share 모드의 Current 블록이 여러 노드에 동시에 캐싱돼 있을수 있다. 
로컬 캐시 관점에서는 여전히 하나지만 글로별 캐시 관점에서 보면 Current 블록이 여러 개인 셈이다. 하지만 Share 모드와 달리 Exclusive 모드의 Current 블록은 오직 한 노드에만 존재할 수 있다. (6장에서 자세히)

Consistent 모드로 데이터를 읽을 때 쿼리 시작 시점과 블록의 마지막 변경시점을 어떻게 확인할까? 오라클은 SCN(System Commit Number)이라고 하는 시간정보를 이용해 데이터베이스의 일관성 있는 상태를 식별하는데 이는 시스템 전체적으로 공유되는 Global 변수라고 이해하면 쉽다. 이름이 의미하듯이 이 값은 기본적으로 사용자가 커빗할 때마다 1씩 증가한다. 또는 커맛이 없더라도 오라클 백그라운드 프로세스에 의해 조금씩증가한다.

h3.(3) Consistent 모드 블록 읽기의 세부원리
오라클에서 수행되는 모든 쿼리는 Global 변수인 SCN(System Commit Number)값을 먼저 확인하고 나서
읽기 작업을 시작하는데 이를 '쿼리 SCN' 또는 '스넵샷 SCN' 이라고 한다.
쿼리 SCN을들고다니면서 읽는 블록마다 블록 SCN과비교해 읽을수 있는버전인지를 판단하는것이다.