# Snapshot too old

ORA-01555는 사람들을 혼란스럽게 하는 오류 중 하나로 여러 오해와 왜곡, 추측을 낳는다.

-Note---------------------------------------------------------------------
ORA-01555는 데이터 훼손이나 데이터 손실과는 아무 관련이 없다.
그런 점에서 '안전한' 오류다.
단지 이 오류를 만나는 순간 쿼리를 계속 진행할 수 없을 뿐이다.
---------------------------------------------------------------------------

ORA-01555 오류의 실질적인 원인은 두 가지지만, 이 오류를 자주 유발하는 특별한 경우를 따로 구분해 세 가지로 설명해보겠다.

    * 작업량에 비해서 언두 세그먼트가 너무 작다.

    * 데이터를 fetch하는 중간중간에 commit(fetch across commit)하도록 프로그래밍했다(실제로는 첫 항목에 대한 변형).

    * 블록 클린아웃

위 두 가지 사항은 오라클의 읽기 일관선 모델과 직접 관련이 있다.
7장 '동시성과 멀티버저닝'에서 설명한 내용을 상기해보자.
쿼리 결과는 미리 확정돼 있고, 다시 말해 오라클이 첫 번째 로우를 검색하러 가기도 전에 결과가 잘 정의되어 있다.
오라클은 쿼리가 시작한 이후로 변경된 블록을 롤백하기 위해 언두 세그먼트를 사용함으로써 데이터베이스의 스냅샷을 일관적인 시점을 기준으로 제공한다.
다음과 같은 문장을 수행한다고 하자.
update t set x = 5 where x = 2;
insert into t select * from t where x = 2;
delete from t where x = 2;
select * from t where x = 2;

위 문장들은 데이터베이스에서 일어나는 다른 어떤 동시 작업에도 영향을 받지 않고 테이블 T의 읽기 일관적인 뷰를 통해 X가 2인 로우 집합을 읽을 것이다.

-Note---------------------------------------------------------------------
방금 본 4개 문장은 테이블 T의 일관적인 뷰를 바라보는 문장의 종류를 보여주는 예제에 불과하며, 데이터베이스에서 단일 트랜잭션으로 실행되는 것을 의미하지는 않는다.
왜냐하면 첫 번째 update가 실행되고 나면 그 다음 세 문장의 WHERE 절을 만족하는 로우가 없기 때문이다.
---------------------------------------------------------------------------

테이블을 '읽는' 모든 문장은 읽기 일관성을 이용하고 있다.
방금 보여준 예제에서 UPDATE는 X가 2인 로우를 찾기 위해 테이블 T를 읽는다(그런 다음에 그 로우를 UPDATE한다).
INSERT는 X가 2인 로우를 찾기 위해 테이블을 읽고 그 로우를 INSERT한다.
언두 세그먼트의 이 두 가지 용도(즉, 실패한 트랜잭션을 롤백하고 읽기 일관성을 제공)가 ORA-01555 오류를 일으킨 것이다.

앞서 말한 리스트에서 세 번째 항목(블록 클린아웃)은 세션이 하나만 존재하는 데이터베이스에서도 ORA-01555를 일으키는 원인이 되므로 특별한 주의가 필요하다.
쿼리하고 있는 테이블을 이 세션이 변경하지 않았는데도 ORA-01555 오류가 일어날 수 있다는 것이다. 믿기지 않는가?
변경되지 않는다고 보장할 수 있는 테이블에 언두 데이터가 왜 필요한지 잠시 후 살펴보게 될 것이다.

실례를 통해 세 가지 경우 모두 살펴보기 전에 ORA-01555 오류에 대한 일반적인 해결책을 함께 공유해보자.

    * UNDO_RETENTION 파라미터를 적절하게 설정하라(가장 오래 수행하는 트랜잭션에 걸리는 시간보다 많게).
       V$UNDOSTAT는 장시간 수행되는 쿼리에 걸리는 시간을 확인하는 데 사용될 수 있다.
       또한 요청한 UNDO_RETENTION을 근거로 필요한 만큼 언두 세그먼트 크기를 증가시킬 수 있도록 디스크에 충분한 공간을 확보하라.

    * 수동 언두 관리를 사용할 때는 언두 세그먼트를 늘리거나 좀 더 많은 언두 세그먼트를 추가하라.
       이것은 장시간 쿼리를 수행하는 동안에 언두 데이터가 덮어 써질 가능성을 줄여준다.
       이 방법은 앞에서 지적한 세 가지 원인 모두의 해결 방안이기도 하다.
       그러나 그리 권장할 만한 방법은 아니며, 자동 언두 관리를 적극 추천한다.

    * 쿼리 수행 시간을 단축시켜라(쿼리를 튜닝하라).
       이것은 항상 최선의 방법이므로 제일 머저 시도해야 한다.
       튜닝은 더 큰 언두 세그먼트에 대한 필요성을 줄여준다.
       이 방법은 위에서 지적한 세 가지 원인 모두의 해결 방안이기도 하다.

    * 관련된 객체에 대하여 통계정보를 수집하라.
       이것은 앞의 리스트 중 세 번째 항목을 피하는 데 도움이 된다.
       블록 클린아웃은 대용량 UPDATE 또는 INSERT의 결과고, 대량의 UPDATE 또는 적재 후에는 어차피 통계를 수집해야 하기 때문이다.

위 해법은 반드시 숙지해야 할 만큼 중요하므로 다시 논의할 것이다.
본격적으로 논의하기 전에 해결책을 먼저 제시하는 것이 좋겟다고 생각했다.


# 언두 세그먼트가 너무 작다
트랜잭션 크기가 작은 시스템이 있다.
그러면 아주 작은 크기의 언두 세그먼트를 할당해야 한다고 생각하기 마련이다.
예를 들면 다음과 같은 시나리오다.

    * 각 트랜잭션은 평균적으로 8KB의 언두를 생성한다.

    * 이런 트랜잭션을 평균적으로 초당 5개 수행한다(초당 40KB의 언두, 분당 2,400KB).

    * 평균적으로 1분에 한 번, 1MB의 언두를 생성하는 트랜잭션을 갖고 있다.
       통틀어서 분당 약 3.5MB의 언두가 생성된다.

    * 시스템에 총 15MB의 언두를 구성한다.

언두(15MB)는 이 데이터베이스에서 트랜잭션 처리에 필요한 양보다 훨씬 많다.
언두 세그먼트는 순환방식으로 평균 약 3~4분마다 언두 공간을 재사용한다.
변경 작업을 하는 트랜잭션에 기초해 언두 세그먼트 크기를 조정했으므로 적절한 조치로 볼 수 있다.

그러나 동일한 환경이더라도 리포트해야 할 일이 생기면, 일부 쿼리는 실행하는 데 정말 오랜 시간이 걸린다(약 5분 남짓). 이것이 발단이다.
쿼리를 실행하는 데 5분이 걸리고 쿼리 시작 시점 기준의 데이터 뷰가 필요하다면, ORA-01555 오류가 발생할 확률이 매우 높다.
언두 세그먼트가 이 쿼리를 실행하는 동안 덮어 쓰여질 것이므로, 쿼리르 시작한 이후로 생성된 언두 정보가 없어질 수 있다.
만약 쿼리를 시작하자마자 변경된 블록을 찾아간다면, 이 블록에 대한 언두 정보는 이미 사라진 상태고 ORA-01555 오류를 만나게 될 것이다.

여기에 간단한 예제가 있다.
블곡이 1,000,000개가 있는 테이블이 있다고 하자.
표 9-2는 일어날 수 있는 일련의 사건을 보여주고 있다.

표 9-2 | 장시간 실행하는 쿼리의 타임라인
-------------------------------------------------------------------------------------------
  시간(분:초) |   진행사항
-------------------------------------------------------------------------------------------
   0:00          | 쿼리를 시작한다.
-------------------------------------------------------------------------------------------
   0:01          | 다른 세션이 블록 1,000,000을 UPDATE한다.
                   | 이 블록에 대한 언두 정보는 언두 세그먼트에 기록한다.
-------------------------------------------------------------------------------------------
   0:01          | 이 UPDATE 세션이 커밋한다.
                   | 이 세션이 생성한 언두 데이터는 아직도 그대로 남아 있지만, 이후로 공간이 필요해지는 순간 언제든 덮어 쓰일 수 있다.
-------------------------------------------------------------------------------------------
   1:00          | 쿼리는 여전히 진행 중이다. 블록 200,000에 도달했다.
-------------------------------------------------------------------------------------------
   1:01          | 많은 활동이 계속 진행 중이다. 이제 14MB를 약간 넘은 양의 언두를 생성했다.
-------------------------------------------------------------------------------------------
   3:00          | 쿼리는 아직도 활발히 진행 중이다. 이제 블록 600,000에 도달했다.
-------------------------------------------------------------------------------------------
   4:00          | 언두 세그먼트가 덮여 쓰여지기 시작하고 0:00 시간에 쿼리를 시작할 때 활성화되어 있던(트랜잭션이 진행 중이던) 공간을 재사용한다.
                   | 특히, 0:01 시간에 블록 1,000,000에 대한 UPDATE 시 사용되었던 언두 세그먼트 공간을 방금 재사용했다.
-------------------------------------------------------------------------------------------
   5:00          | 쿼리가 드디어 블록 1,000,000에 도달했다.
                   | 쿼리가 시작한 이후로 그 블록이 변경되었음을 발견한다.
                   | 언두 세그먼트로 가서 그 블록에 대한 일관적인 읽기를 위한 그 블록에 대한 언두를 찾으려고 한다.
                   | 이 시점에, 필요한 그 정보가 더 이상 존재하지 않음을 발견하고는 ORA-01555 오류와 함께 쿼리는 실패한다.
-------------------------------------------------------------------------------------------

여기까지다. 만약 쿼리를 실행하는 동안 덮여 쓰여질 가능성이 있는 크기로 언두 세그먼트를 설정한 상태에서 변경될 수도 있는 데이터를 그 쿼리가 액세스한다면, ORA-01555 오류를 되풀이할 가능성이 매우 크다.
만약 이런경우라면, UNDO_RETENTION 파라미터를 더 높게 설정해서 얼마큼의 언두를 보유해야 하는지 오라클 스스로 파악하도록 해라(필자는 이런 접근방법을 제안한다. 실제 필요한 언두의 크기를 스스로 파악하는 것보다 훨씬 쉽다).
또는 언두 세그먼트 크기를 다시 조정해서 더 크게 만들어라(또는 언두 세그먼트를 더 많이 만들어라).
장시간 수행되는 쿼리 시간만큼 유지되도록 언두를 충분히 크게 구성할 필요가 있다.
앞에서는 트랜잭션에만 맞춰 크기를 조정하였다.
시스템의 다른 컴포넌트를 감안해 조정해야 함을 잊어버렸다.

오라클 9i 이상부터는 시스템에서 언두를 관리하는 방법이 두 가지 있다.

    * 자동 언두 관리 : UNDO_RETENTION 파라미터를 통해 오라클이 얼마 동안 언두를 유지해야 하는지 지정한다.
      오라클은 동시 작업량에 기반해 언두 세그먼트가 얼마나 필요한지, 각 언두 세그먼트 크기를 얼마로 해야 하는지를 결정한다.
      데이터베이스는 이와 더불어 DBA가 설정한 UNDO_RETENTION 목표치에 맞추기 위해 실행 시에 개별 언두 세그먼트 간에 익스텐트를 재할당할 수 있다.
      이 관리방법은 언두 관리를 위해서 권장하는 접근 방식이다.

   * 수동 언두 관리 : DBA가 언두를 관리한다.
      DBA는 예측된 또는 관찰된 작업량에 기반하여 수동으로 얼마나 많은 언두 세그먼트를 만들지를 결정한다.
      DBA는 트랜잭션 볼륨(언두가 얼마나 생성되는지)을 기반으로 세그먼트 크기를 정한다.

자동 언두 관리에서 ora-01555 오류를 피하는 일은 매우 쉽다.
언두 공간을 얼마나 확장할 수 있는지 파악하고 미리 할당하는 것보다는 DBA가 데이터베이스에게 쿼리가 오래 실행하는지 알려주고 UNDO_RETENTION 파라미터 값을 설정하는 것이 낫다.
오라클은 적어도 그 시간 동안만큼은 언두를 보존하려 한다.
만약 늘어날 공간이 충분히 할당되었다면, 오라클은 언두 세그먼트를 확장하면서 최대한 언두를 재사용하지 않을 것이다(UNDO_RETENTION 기간을 따르기 위해서).
이것은 될 수 있으면 빨리 순환해서 언두 공간을 재사용하는 수동 언두 관리와 직접적으로 대조된다.
주로 이런 이유 때문에 UNDO_RETENTION 파라미터를 지원하며, 필자는 가능할 때마다 자동 언두 관리를 적극 추천한다.
UNDO_RETENTION 파라미터는 ORA-01555 오류가 발생할 가능성을 훨씬 줄여준다(물론 적절하게 설정되었을 때).

수동 언두 관리를 사용할 때 ORA-01555이 발생할 확률은 시스템에서 가장 크거나 또는 평균적인 언두 세그먼트가 아니라 가장 작은 언두 세그먼트에 의해 좌우된다.
'큰' 언두 세그먼트를 추가 했다고 해서 이 문제가 사라지지 않는다.
수동 언두 관리는 쿼리를 처리하는 동안 가장 작은 언두 세그먼트를 선택해서 순환하기 때문에 ORA-01555가 발생할 가능성이 있다.
이것이 필자가 기존 언두 세그먼트를 사용할 때 롤백 세그먼트 크기를 모두 같게 설정하는 이유다.
이 방식에선 가장 크거나 작은 언두 세그먼트 없이 모두 동일한 크기를 유지한다.
이것은 또한 자동으로 '최적의' 언두 세그먼트 크기를 유지하는 기능을 꺼리게 만드는 이유다.
만약 어렵게 늘어난 언두 세그먼트를 다시 감소시키면, 금방 필요할지 모를 많은 양의 언두를 버린 결과를 초래할 수 있다.
그래서 가장 오래된 언두 데이터부터 버리도록 되어 있지만 여전히 위험은 존재한다.
어쩔 수 없다면 피크타임을 피해 수동으로 언두 세그먼트를 줄여주는 것이 좋다.

DBA 역할에 너무 깊이 들어간 듯해서 이쯤에서 그 다음 경우로 옮겨보자.
강조하고자 한 것은 여기서 발생한 ORA-01555 오류는 작업량에 비해 언두 세그먼트르 너무 작게 설정한 데서 기인한다는 사실이다.
유일한 해결책은 작업량에 맞는 크기로 설정하는 것이다.
누구의 귀책인지를 따질 의도는 없지만, 오류를 만나게 되면 문제가 된다.
쿼리하는 동안 임시 공간이 소진되는 것과 같은 유형의 문제다.
시스템에 대한 임시 공간을 충분하게 구성하든가 쿼리를 재작성하여 임시 공간을 요구하지 않는 실행계획을 사용하도록 해야 한다.

이런 효과를 보여주기 위해서는 작지만 조금은 인위적인 테스트를 설정할 수 있다.
아주 작은 언두 테이블스페이스를 생성한다.
그리고 작은 트랜잭션을 많이 발생시킨다.
그러면 그 세션은 할당된 언두 공간을 여러 번 순환하며 재사용하게 된다(언두 테이블스페이스가 증가하는 것을 허용하지 않으므로 UNDO_RETENTION 설정과 관계없다).
언두 세그먼트를 사용하는 세션 A는 테이블 T를 변경할 것이다.
세션 A는 테이블 T를 전체 스캔하여 읽을 것이다.
다른 세션 B에서는 인덱스를 통해 테이블 T를 읽는 쿼리를 실행할 것이다.
세션 B는 조금은 무작위(로우 1, 로우 1,000, 로우 500, 그런 다음 20,001 등)로 테이블을 읽을 것이다(object_id 순서대로).
확률은 거의 100%다.
세션 A에서 다음을 시작해보자.
SUNSHINY@ORACLE11> create undo tablespace undo_small
  2  datafile '/data/oradata/oracle11g/undo.dbf' size 4m
  3  autoextend off
  4  /

Tablespace created.

SUNSHINY@ORACLE11> alter system set undo_tablespace = undo_small;

System altered.

이제 쿼리하고 변경할 테이블 T를 만든다.
이 테이블에서는 무작위로 데이터를 정렬할 것이다.
CREATE TABLE AS SELECT는 쿼리에서 로우를 가져오는 순서대로 블록에 삽입할 것이다.
다만, 무작위로 분포시키면서 특정 순서에 따라 인위적으로 정렬되지 않도록 로우를 완전히 뒤섞을 것이다.
SUNSHINY@ORACLE11> create table t
        as
        select *
        from all_objects
        order by dbms_random.random;

Table created.

SUNSHINY@ORACLE11> alter table t add constraint t_pk primary key(object_id)
  2  /

Table altered.

SUNSHINY@ORACLE11> exec dbms_stats.gather_table_stats( user, 'T', cascade=> true );

PL/SQL procedure successfully completed.

이제 테이블 T에 대한 데이터 변경 작업을 실행한다.
-- 1. 세션 A 실행
SUNSHINY@ORACLE11> begin               
  2      for x in ( select rowid rid from t )           
  3      loop                                             
  4          update t set object_name = lower(object_name) where rowid = x.rid;
  5          commit;       
  6      end loop;         
  7  end;         
  8  /

PL/SQL procedure successfully completed.

이제 PL/SQL 코드 블록이 실행하는 동안 세션 B에서 쿼리를 실행할 것이다.
그 쿼리는 테이블 T를 읽고 각 로우를 처리한다.
다음 로우를 가져오기 전에 각 로우를 처리하는 데 약 1/100초 정도 걸릴 것이다.
이런 상황을 시뮬레이션하려고 DBMS_LOCK.SLEEP(0.01)을 사용했다.
OBJECT_ID 순으로 정렬된 인덱스를 통해 테이블 로우를 읽게 하려고 인덱스를 생성하고, 쿼리가 그 인덱스를 이용하도록 FIRST_ROWS 힌트를 사용할 것이다.
테이블에 데이터를 무작위로 삽입했으므로 상당히 랜덤하게 테이블에서 블록을 쿼리할 것이고, 단지 2~3초 정도 실행하다가 결국은 실패할 것이다.
-- 2. 세션 B 실행
SUNSHINY@ORACLE11> declare
  2      cursor c is
  3      select /*+ first_rows */ object_name
  4        from t
  5       order by object_id;
  6  
  7      l_object_name t.object_name%type;
  8      l_rowcnt      number := 0;
  9  
 10  begin
 11      open c;
 12      loop
 13          fetch c into l_object_name;
 14          exit when c%notfound;
 15          dbms_lock.sleep( 0.01 );
 16          l_rowcnt := l_rowcnt+1;
 17      end loop;
 18      close c;
 19  exception
 20      when others then
 21          dbms_output.put_line( 'rows fetched = ' || l_rowcnt );
 22          raise;
 23  end;
 24  /
rows fetched = 7766
rows fetched = 731
declare
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 19 with name "_SYSSMU19_2301456657$" too small
ORA-06512: at line 22


ORA-01555(snapshot to old error)로 실패했다.
이것을 고치기 위해서는 아래 두 가지가 먼저 선행되어야 한다.

    * 적어도 이 읽기 프로세스가 충분히 완료할 수 있을 만큼 데이터베이스의 UNDO_RETENTION을 크게 설정해야 한다.
       UNDO_RETENTION을 크게 하면 프로세스를 완료할 때까지 충분한 언두를 보유하도록 데이터베이스가 언두 테이블스페이스를 증가시킬 수 있도록 한다.

    * 언두 테이블스페이스가 증가하도록 설정하든가 수동으로 디스크 공간을 더 많이 할당한다.

예를 들면 필자는 오랫동안 실행하는 프로세스를 완료하는 데 약 720초 걸린다고 판단했다(테이블에 72,000개의 레코드가 있고 로우당 0.01초로 따지면 720초가 된다).
또한 한 번에 1MB씩 증가해서 2GB까지 증가하도록 언두 테이블스페이스의 데이터 파일을 변경했다.
SUNSHINY@ORACLE11> alter database 
  2  datafile '/data/oradata/oracle11g/undo.dbf'
  3  autoextend on
  4  next 1m
  5  maxsize 2048m;

Database altered.

SUNSHINY@ORACLE11> select bytes/1024/1024
  2    from dba_data_files
  3  where tablespace_name = 'UNDO_SMALL';

BYTES/1024/1024
---------------
              5


-- 1. A 세션 실행
SUNSHINY@ORACLE11> begin 
  2      for x in ( select rowid rid from t )
  3      loop
        update t set object_name = lower(object_name) where rowid = x.rid;
  4    5          commit;
  6      end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SUNSHINY@ORACLE11> 


-- 2. B 세션 실행
SUNSHINY@ORACLE11> declare
  2      cursor c is
  3      select /*+ first_rows */ object_name
  4        from t
  5       order by object_id;
  6  
  7      l_object_name t.object_name%type;
  8      l_rowcnt      number := 0;
  9  
 10  begin
 11      open c;
 12      loop
 13          fetch c into l_object_name;
 14          exit when c%notfound;
 15          dbms_lock.sleep( 0.01 );
 16          l_rowcnt := l_rowcnt+1;
 17      end loop;
 18      close c;
 19  exception
 20      when others then
 21          dbms_output.put_line( 'rows fetched = ' || l_rowcnt );
 22          raise;
 23  end;
 24  /


PL/SQL procedure successfully completed.


다시 프로세스 2개를 동시에 실행해보니 둘 다 완료했다.
이번에는 언두 테이블스페이스의 데이터 파일이 증가했는데, 언두 보유 시간을 설정해서 언두가 증가하도록 했기 때문이다.
SUNSHINY@ORACLE11> select bytes/1024/1024
  2    from dba_data_files
  3  where tablespace_name = 'UNDO_SMALL';

BYTES/1024/1024
---------------
             24


그래서 오류를 받지 않고 성공적으로 작업을 마쳤으며, 언두는 작업 요구량을 수용할 만큼 적절하게 증가했다.
이 예제에서 오류가 난 것은 인덱스를 경유해 테이블을 읽게 함으로써 테이블 T 전체를 무작위로 읽도록 했기 때문이다.
만일 세션 B에서 인덱스를 경유하지 않고 테이블을 빠르게 전체 스캔한다면, ORA-01555 오류를 만나지 않을 가능성이 크다.
왜냐하면 SELECT와 UPDATE 둘 다 테이블 T를 전체 스캔하지만, SELECT가 스캔하는 동안 UPDATE를 앞질러 갈 수 있기 때문이다(SELECT는 단지 읽기만 하면 되지만, UPDATE는 읽고 나서 수정해야 하므로 느릴 수 있다).
무작위로 읽도록 했기 때문에 UPDATE가 많은 로우를 변경하고 커밋까지 한 블록을 SELECT가 방문할 확률이 증가한다.
이는 늘 방심할 수 없는 ORA-01555 오류의 특성을 보여준다.
이 오류의 발생 여부는 동시 세션이 해당 테이블을 얼마나 액세스하고 처리하는지에 달렸다.


# 지연된 블록 클린아웃
ORA-01555 오류의 발생 원인을 완전히 제거하기는 어렵지만 이 오류가 그렇게 자주 일어나지는 않는다(적어도 오라클 8i 이후에서는 그렇다).
이미 블록 클린아웃 메커니즘은 논의했지만, 요약하자면 블록을 변경한 후 이 블록을 액세스하는 다음 세션이 최종으로 블록을 변경한 트랜잭션이 여전히 활동 중인지 체크할 수 있다.
블록을 변경한 트랜잭션이 활동하지 않는다고 확인되면, 그 블록을 액세스하는 다음 세션이 똑같은 과정을 다시 겪지 않도록 해당 블록을 클린아웃한다.
블록을 클링아웃하기 위해 오라클은 이전 트랜잭션이 사용한 언두 세그먼트를 확인(블록 헤더로부터)하고 언두 헤더를 찾아가 해당 트랜잭션이 커밋했는지, 키밋했다면 언제 커밋했는지를 확인한다.
확인하는 방법은 두 가지가 있다.
하나는 오라클이 언두 세그먼트 트랜잭션 테이블에서 해당 트랜잭션 슬롯이 덮어 써졌을 때라도 그 트랜잭션이 오래 전에 커밋했다고 확인할 수 있다.
또 다른 방법은 트랜잭션이 좀 전에 커밋했다는 것을 의미하는 COMMIT SCN이 언두 세그먼트 트랜잭션 테이블에 존재하는지와 그 트랜잭션 슬롯이 덮어 써지지 않았다는 것을 확인하는 것이다.

지연된 블록 클린아웃으로 인해 ORA-01555 오류가 발생하려면 다음 조건을 모두 만족해야 한다.

    * 변경하고 커밋도 했지만 블록은 자동으로 클린아웃되지 않았다(예:트랜잭션이 SGA 블록 버퍼 캐시의 10%를 초과하는 블록을 변경했다).

    * 클린아웃되지 않은 블록을 방문하는 세션이 장시간 존재하지 않았다.

    * 오랜 시간이 걸리는 쿼리가 시작된다.
       이 쿼리가 시작된 직후, 클린아웃되지 않은 일부 블록을 읽는다.
       이 쿼리는 SCN t1에 시작했다. 즉, 읽기 일관성을 달성하기 위해 SCN이 가리키는 t1 시점으로 롤백한 읽기 일관적인 데이터 뷰를 제공해야 한다.
       그 변경 트랜잭션에 대한 트랜잭션 엔트리는 쿼리가 시작할 때 여전히 언두 세그먼트 트랜잭션 테이블에 있다.

    * 쿼리를 실행하는 동안에 시스템에서 커밋이 많이 일어나지만, 이 트랜잭션은 해당 블록을 방문하지 않는다(만약 방문했다면, 오래된 트랜잭션 결과를 정리하는 과정에서 겪게 되는 클린아웃 이슈가 발행하지 않을 것이다).

    * 언두 세그먼트에서 트랜잭션 테이블은 순환하면서 잦은 커밋으로 인해 슬롯을 재사용한다.
       본래 블록을 변경한 트랜잭션의 엔트리는 반복적으로 순환되며 재사용된다는 것이 가장 중요하다.
       게다가, 시스템이 언두 세그먼트 익스텐트를 재사용하다 보니 언두 세그먼트 헤더 블록 자체에 대한 일관성 읽기가 불가능하게 됐다.

    * 게다가 언두 세그먼트에 기록된 가장 작은 SCN이 너무 잦은 커밋으로 인하여 t1(쿼리의 읽기 일관성 SCN보다 큼)보다 커졌다.

쿼리가 시작하기 전에 변경되고, 커밋된 블록을 방문했을 때 문제가 터진다.
정상적으로 그 블록이 가리키는 언두 세그먼트로 가서 그 블록을 변경한 트랜잭션의 상태를 알아본다(바꾸어 말하면, 그 트랜잭션의 COMMIT SCN을 알아낸다).
만약 COMMIT SCN이 t1보다 작다면, 쿼리는 그 블록을 사용할 수 있다.
COMMIT SCN이 t1보다 크다면, 쿼리는 그 블록을 롤백해야 한다.
그러나 쿼리가 위와 같이 특별한 경우에는 그 블록의 COMMIT SCN이 t1보다 큰지 작은지를 결정할 수 없다.
따라서 그 블록 이미지를 사용할 수 있는지 없는지를 확신할 수 없다.
그래서 결론적으로 ORA-01555 오류가 발생한다.

......
만일 별다른 DML이 없는 테이블을 대상으로 한 SELECT가 ORA-01555 오류를 발생시키는 이슈와 마주쳤다면, 다음 해결책을 시도해보라.

    * 우선 '적절한 크기의' 트랜잭션을 사용하는지 확인하라. 필요 이상으로 너무 자주 커밋하지 않는지 확인하라.

    * 관련된 객체를 스캔하기 위해 적재 후에 그 객체를 클린아웃하는 DBMS_STATS를 사용하라.
       블록 클린아웃은 대용량 UPDATE 또는 INSERT의 결과로 인한 것이므로 통계정보 수집은 어차피 필요한 작업이다.

    * 언두 테이블스페이스가 확장할 공간을 제공하고 언두 보유시간을 늘려라.
       이것은 장시간 수행하는 쿼리 도중에 언두 세그먼트 트랜잭션 테이블 슬롯이 덮어 써질 확률을 줄여준다.
       이것은 ORA-01555 오류의 다른 원인에 대한 해결책과 동일하다(두 개는 매우 밀접하게 관련되어 있고, 쿼리를 처리하는 동안에 언두 세그먼트 재사용을 경험하게 된다).
       실제로 UNDO Retention을 900초로 설정하고 한번에 1MB씩 자동으로 확장하도록 설정된 언두 테이블슾이스 위 예제를 재실행해보면, 테이블 BIG에 대한 쿼리는 성공적으로 완료했다.

    * 쿼리의 실행 시간을 줄여라(쿼리를 튜닝하라).
       이것은 항상 최선의 해결책이며, 가장 우선적으로 시도해볼 수 잇는 작업일 것이다.



출처 : 전문가를 위한 오라클 데이터베이스 아키텍처 - 토마스 카이트


※ 위 내용은, 여러 자료를 참고하거나 제가 주관적으로 정리한 것입니다.
   잘못된 정보나 보완이 필요한 부분을, 댓글 또는 메일로 보내주시면 많은 도움이 되겠습니다.
05 22, 2012 11:10 05 22, 2012 11:10


Trackback URL : http://develop.sunshiny.co.kr/trackback/768

  1. # 배그핵 2018年 11月 10日 19時 50分 Delete Reply

    안녕하세요^^

Leave a comment

« Previous : 1 : ... 218 : 219 : 220 : 221 : 222 : 223 : 224 : 225 : 226 : ... 648 : Next »

Recent Posts

  1. HDFS - Python Encoding 오류 처리
  2. HP - Vertica ROS Container 관련 오류...
  3. HDFS - Hive 실행시 System Time 오류
  4. HP - Vertica 사용자 쿼리 이력 테이블...
  5. Client에서 HDFS 환경의 데이터 처리시...

Recent Comments

  1. 안녕하세요^^ 배그핵
  2. 안녕하세요^^ 도움이 되셨다니, 저... sunshiny
  3. 정말 큰 도움이 되었습니다.. 감사합... 사랑은
  4. 네, 안녕하세요. 댓글 남겨 주셔서... sunshiny
  5. 감사합니다 많은 도움 되었습니다!ㅎㅎ 프리시퀸스

Recent Trackbacks

  1. chatbots chatbots %M
  2. london relocation agents london relocation agents %M
  3. how to build chatbot how to build chatbot %M
  4. advanced chatbot advanced chatbot %M
  5. facebook ai chatbot facebook ai chatbot %M

Calendar

«   08 2019   »
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

Bookmarks

  1. 위키피디아
  2. MysqlKorea
  3. 오라클 클럽
  4. API - Java
  5. Apache Hadoop API
  6. Apache Software Foundation
  7. HDFS 생태계 솔루션
  8. DNSBL - Spam Database Lookup
  9. Ready System
  10. Solaris Freeware
  11. Linux-Site
  12. 윈디하나의 솔라나라

Site Stats

TOTAL 2660955 HIT
TODAY 1271 HIT
YESTERDAY 1540 HIT