3. 버퍼 Lock

(1) 버퍼 Lock이란?

  • 캐시된 버퍼 블록을 읽거나 변경하려는 프로세스는 먼저 버퍼 헤더로부터 버퍼 Lock을 획득해야 함. 버퍼 Lock을 획들했다면 래치를 곧바로 해제
  • Share 모드 : 버퍼 내용을 읽기만 할때는 share 모드, 변경할 때는 Exclusive 모드로 Lock을 설정
  • select문이라도 블록 클린아웃(cleanout)이 필요할 때는 버퍼 내용을 변경하는 작업이므로 Exclusive 모드 Lock을 요구
  • 만약 해시 체인 래치를 획득하고 목적한 버퍼를 찾았는데 다른 프로세스가 버퍼 Lock을 Exclusive 모드로 점유한 채 내용을 갱신 중이라면 래치를 쥔 채 기다릴 수는 없음. 그럴 때는 버퍼 헤더에 잇는 버퍼 Lock 대기자 목록(Waiter List)에 자신을 등록하고 일단 래치는 해제. 버퍼 Lock 대기자 목록에 등록돼 있는 동안 buffer busy waits 대기 이벤트가 발생
  • 대기자 목록에서 기다리다가 버퍼 Lock이 해제되면 버퍼 Lock을 획득하고, 원했던 작업 진행
  • 목적한 읽기/쓰기 작업을 완료하면 버퍼 헤더에서 버퍼 Lock을 해제해야 하는데, 이때도 버퍼 헤더를 액세스하려는 다른 프로세스와 충돌이 생길 수 있으므로 해당 버퍼가 속한 체인 래치를 다시 한번 획득
  • 버퍼 Lock을 해제하고 래치를 해제해야 비로소 버퍼 블록 읽기가 완료됨

(2) 버퍼 핸들

  • 버퍼 Lock을 설정하는 것은 자신이 현재 그 버퍼를 사용중임을 표시해 두는 것으로서, 그 버퍼 헤더에 Pin을 걸었다고도 표현.
  • 버퍼 Lock = 버퍼 Pin = Pinned 버퍼
  • 변경 시에는 하나의 프로세스만 Pin을 설정할 수 있지만 읽기작업을 위해서라면 여러 개의 프로세스가 동시에 Pin 설정 가능
  • 버퍼 핸들 (Buffer Handle) : 버퍼에 Pin을 설정하려고 사용하는 오브젝트. 버퍼 핸들을 얻어 버퍼 헤더에 있는 소유자 목록(Holder Lis)에 연결시키는 방식으로 Pin 설정
  • cache buffer handle : 버퍼 핸들도 공유된 리소스이므로 버퍼 핸들을 얻을때 필요한 래치. 버퍼를 Pin하는 오퍼레이션이 많을수록 오히려 cache buffer handles 래치가 경합지점이 될 것이므로 오라클은 각 프로세스마다 _db_handles_cached 개수만큼의 버퍼 핸들을 미리 할당해 주며, 기본 값은 5개
  • 각 세션은 이를 캐싱하고 있다가 버퍼를 Pin 할 때마다 사용하며, 그 이상의 버퍼 핸들이 필요할 때만 cache buffer handles 래치를 얻고 추가로 버퍼 핸들을 할당 받음
  • 시스템 전체적으로 사용할 수 있는 총 버퍼 핸들 개수는 _db_handles 파라미터에 의해 결정되며, 이는 processes 파라미터와 _db_handles_cached 파 라미터를 곱한 값으로 설정

(3) 버퍼 Lock의 필요성

  • 오라클이 하나의 레코드를 갱신하더라고 블록 단위로 I/O를 수행하기 때문에 사용자 데이터를 변경할 떄 DML Lock을 통해 보호하도록 되어있으며, 그것을 담는 블록에 또 다른 Lock을 획득해야 함
  • 값을 변경하기 전에 레코드에 로우 단위 Lock을 설정하는 일 자체도 레코드의 속성을 변경하는 작업이므로 두 개의 프로세스가 동시에 로우 단위 Lock을 설정하려고 시도한다면(대상 로우가 다르더라도) 문제가 됨
  • 블록 SCN을 변경하거나 ITL 슬롯에 변경을 가하는 등 블록 헤더 내용을 변경하는 작업도 동시에 일어날 수 있는데, 이런 동시 액세스가 실제로 발생한다면 Lost Update 문제가 생겨 블록 자체의 정합성이 깨지게 되므로 블록 자체로의 진입을 직렬화해야 함
  • Pin된 버퍼 블록은, 버퍼 캐시 전체를 비우려고 SQL> ALTER SYSTEM FLUSH BUFFER_CACHE; 시스템 명령어를 날리더라도 밀려 나지 않음

(4) 버퍼 Pinning

  • 버퍼를 읽고 나서 버퍼 Pin을 즉각 해제하지 않고 데이터베이스 Call이 진행되는 동안 유지하는 기능
  • 블록을 반복적으로 읽을 때 버퍼 Pinning을 통해 래치 획득 과정을 생략한다면 논리적인 블록 읽기(Logical reads)횟수를 획기적으로 줄 일 수 있음. 모든 버퍼 블록을 이 방식으로 읽는 것이 아니며, 같은 블록을 재방문할 가능성이 큰 몇몇 오퍼레이션을 수행할 때만 사용
  • 래치 획득 과정을 통해 블록을 액세스할 때는 session logical reads 항목이 증가하고, 래치 획득 과정 없이 버퍼 Pinning 을 통해 블록을 곧바로 액세스할 때는 buffer is pinned count 항목의 수치가 증가함
  • 버퍼 Pinning은 하나의 데이터베이스 Call(Prase Call, Execute Call, Fetch Call)내에서만 유효함
  • Call이 끝나고 사용자에게 결과를 반환하고 나면 Pin은 해제되어야 함. 따라서 첫 번째 Fetch Call에서 Pin된 블록은 두 번째 Fetch Call에서 다시 래치 획득 과정을 거쳐 Pin되어야 함
  • 전통적으로 버퍼 Pinning이 적용되던 지점은 인덱스를 스캔하면서 테이블을 액세스할 때의 인덱스 리프 블록임. Index Range Scan하면서 인덱스와 테이블 블록을 교차방문 할 때 블록 I/O를 체크해 보면, 테이블 블록에 대한 I/O만 계속 증가하는 이유가 여기에 있음
  • 인덱스를 경유해 테이블을 액세스할 때 인덱스 클러스터링 팩터가 좋다면 (인덱스 레코드가 가리키는 테이블 rowid 정렬 순서가 인덱스 키 값 정렬 순서와 거의 일치한다면) 같은 테이블 블록을 반복 액세스할 가능성이 그만큼 커짐
  • 오라클 8i부터, 인덱스로부터 액세스되는 하나의 테이블 블록을 Pinning하기 시작
  • 9i 부터는 NL, 조인 시 Inner 테이블을 룩업하기 위해 사용되는 인덱스 루트 블록을 Pinning 하기 시작. 9i에 도입된 Index Skip Scan에서 브랜치 브록을 거쳐 리프 블록을 액세스하는 동안에도 브랜치 블록을 계속 Pinning하고 있다가 그 다음 방문할 리프 블록을 찾으려 할때 추가적인 래치 획득 과정없이 브랜치 블록을 곧바로 읽음
  • 11g 부터는 NL 조인 시 Inner 테이블의 인덱스 루트 블록 뿐 아니라 다른 인덱스 블록에 대해서도Pinning을 함으로써 논리적 블록 읽기를 획기적으로 감소시킴
  • DML 수행 시 Undo 레코드를 기록하는 Undo 블록에 대해서도 Pinning을 적용