IT

[Oracle] 오라클 에러 ORA-01555: Snapshot too old

정미나 2018. 4. 18. 19:30

Snapshot too old, 말 그대로 Snapshot이 너무 오래됐다는 말이다.

Snapshot이 뭔데?

* Snapshot : Data Buffer Cache 블록에 갱신이 일어날 때 복사해 놓은 Copy본 (CR 블록)

CR 블록이 뭔지 모르겠다면 

[Oracle] 오라클 다중 버전 읽기 일관성 모델 알아보기


그럼 오라클에서 이런 에러가 나는 이유에 대해 알아보도록 하자.


Undo 실패

- 데이터를 읽어 내려가다가 쿼리 SCN 이후에 변경된 블록을 만나 과거 시점으로 롤백한 'Read Consistent' 이미지를 얻으려고 하는데, Undo 블록이 다른 트랜잭션에 의해 이미 재사용 돼 필요한 Undo 정보를 얻을 수 없는 경우 (Undo 세그먼트가 너무 작을때)

① SCN 123 시점에 1시간 쯤 걸리는 조회 쿼리가 시작됨

② 쿼리가 진행되는 동안 특정 row의 값이 Update 된 후 Commit 됨

→ 그 row를 담고있던 블록의 SCN이 129로 변경됨

③ 얼마후 ②번의 내용을 담고 있던 Undo 블록이 다른 트랜잭션에 의해 재사용

④ ①에서 돌던 쿼리가 특정 row에 도달했을때는 이미 Undo 블록이 덮어쓰여버린 후

▶ Snapshot too old 에러 발생, ①번 쿼리 실행 멈춤


1 for C in (select /*+ ordered use nl(b) */ A.고객ID,A.입금액,B.수납액

2             from 은행입금 A, 수납 B

3            where A.입금일자 = trunc(sysdate)

4              and B.수납년월(+) = to char(sysdate, 'yyyymm')

5              and B.고객ID(+) = A.고객ID)

6 loop

7    if C.수납액 IS NULL then

8       insert into 수납(고객ID,수납년월, 수납액)

9       values ( C.고객ID, to char(sysdate, 'yyyymm'), C.입금액)

10   else

11      update 수납 set 수납액 = 수납액 + C.입금액

12       where 고객ID = C.고객ID

13         and 수납년월 = to_char(sysdate, 'yyyymm')

14   end if;

15   commit;

16 end loop;

① SCN 100 시점에 커서 C 오픈

② 11~13번 라인 update 문에 의해 홍길동 고객의 수납액이 변경되고 15번 라인에서 커밋

→ 변경된 레코드가 담긴 500번 블록 SCN이 120으로 변경됨

* 프로그램 내에서 커밋을 반복하기 때문에 위 insert문과 update문은 모두 개별 트랜잭션 처리

③ 트랜잭션이 계속 반복되기 때문에 홍길동 고객의 수납액 Before Image가 담긴 Undo 블록이 다른 트랜잭션에 의해 재사용 되는 것은 시간 문제, 결국 재사용 되어버림

④ 커서 C가 홍길동 고객과 같은 500번 블록에 저장된 김철수의 수납액을 변경하려고 하는 찰나 Snapshot too old 에러 발생

※ fetch across commit

명시적으로 커서를 열어 row를 하나씩 Fetch하면서 값을 변경하고 loop 내에서 계속해서 commit을 날리는 방식


블록 클린아웃 실패

* 가정: 대량 업데이트 후 커밋된 트랜잭션이 블록 클린아웃 없이 자신이 사용하던 트랜잭션 테이블 슬롯을 Free 상태로 변경

① SCN 90 시점에 쿼리 수행, 클린아웃 되지 않은 블록 Access

② Delayed 블록 클린아웃을 위해 트랜잭션 테이블 슬롯 방문했으나 해당 슬롯이 다른 트랜잭션에 의해 이미 재사용되고 없음

→ 정상적인 블록 클린아웃과 일관성 모드 읽기가 불가능해질 수 있음

③ Undo 레코드에 기록된 Undo 세그먼트 헤더 블록 갱신 내역을 찾아 롤백 시도


* 사실상 블록 클린아웃에 의한 Snapshot too old 에러는 거의 발생하지 않음

- 트랜잭션 슬롯 필요 시 커밋 SCN이 가장 낮은 트랜잭션 슬롯부터 재사용

- 그 슬롯에 기록돼 있던 커밋 SCN을 Undo 세그먼트 헤더에 'low commit SCN'으로 기록

- 아직 클린아웃되지 않은 블록을 클린아웃하려고 ITL이 가리키는 트랜잭션 테이블 슬롯을 찾아갔을 때 커밋 정보가 이미 지워지고 없으면, Undo 세그먼트 헤더에 있는 'low commit SCN'을 블록 ITL 엔트리에 커밋 SCN으로서 기록(추정된 커밋 SCN: C-U-)

(대부분의 low commit SCN은 쿼리 SCN보다 작으므로 정상적인 일관성 모드 읽기가 가능)

* low commit SCN이 쿼리 SCN보다 높아질 정도로 트랜잭션이 몰리는 경우에만 에러 발생


Snapshot too old 회피 방법

- 불필요한 커밋 지양

- fetch across commit 형태의 프로그램 작성 지양

- 트랜잭션이 몰리는 시간대에 오래 걸리는 쿼리 수행 지양

- 큰 테이블을 일정 범위로 나누어 읽고 단계적으로 실행할 수 있도록 코딩

- Nested Loop Join 지양

- order by 강제 삽입 (서버 내에서 빠르게 데이터를 읽어 Temp 세그먼트에 저장)

- 대량 업데이트 후 해당 테이블에 대해 Full Scan 유도