지난 번 학습했던 1절을 이어서 학습했습니다.
1.3.6. ✍️ Single Block I/O vs Multi Block I/O
지난 번 시간에 데이터 Block은 데이터를 읽고 쓰는 최소 단위라고 정의하였다.
일반적으로 메모리 캐시가 클수록 좋지만 비용과 기술적인 한계로 전체 데이터를 캐시에 전부 적재할 수는 없다.
캐시에서 찾지 못한 데이터 블록은 I/O Call을 통해 디스크에서 DB버퍼캐시로 적재하고서 읽는다.
I/O Call 수행 시 한 번에 한 블록 혹은 여러 블록을 요청한다.이때, 나온 방식이 표제의 건이다.
Single Block I/O : I/O Call 요청 시 한 번에 한 블록씩 요청하는 방식
Multi Blcok I/O : 한 번에 여러 블록씩 요청하여 메모리에 적재하는 방식인덱스는 보통 소량의 데이터를 읽을 때 주로 사용하므로, 기본적으로 인덱스와 테이블 블록 모두 Single Block I/O를 사용한다.
Single Blcok I/O 대상 오퍼레이션
- 인덱스 루트 블록을 읽을 때
- 인덱스 루트 블록에서 얻은 주소 정보로 브랜치 블록을 읽을 때
- 인덱스 브랜치 블록에서 얻은 주소 정보로 리프 블록을 읽을 때
- 인덱스 리프 블록에서 얻은 주소 정보로 테이블 블록을 읽을 때
이와 반대로, 많은 데이터 블록을 읽을 때는 Multi Block I/O 방식이 효율적이다. 그래서 인덱스를 이용하지 않고 테이블 전체를 스캔할 때 이 방식을 사용한다.
읽고자 하는 블록을 DB 버퍼캐시에서 찾지 못하면 해당 블록을 디스크에서 읽기 위해 I/O Call을 한다. 그동안 프로세스는 대기 큐에서 대기를 한다.-> 대용량 테이블을 Full Scan할 때 Multi Block I/O 단위를 크게 설정하면 성능이 좋아짐.
정리를 하자면, Multi Block I/O는 캐시에서 찾지 못한 특정 블록을 읽으려고 I/O Call을 할때 디스크 상에서 그 블록과 인접한 블록(같은 익스텐트에 속한 블록)들을 한꺼번에 읽어 캐시에 미리 적재하는 기능.
결국 Multi Block I/O 방식으로 읽어도 익스텐트 경계를 넘지 못한다.
오라클에서 한 번 담을 수 있는 양을 조절하는 파라미터는 db_file_multiblock_read_count이다.
Q. Multi Block I/O 중간에 왜 Single Block I/O가 나타나는가?
-> 익스텐트 맵 : 테이블에 대한 인덱스
-> Multi Block I/O : 배치 I/O배치 단위로 I/O를 수행하게 되면 결국 익스텐트 맵으로 블록 목록을 확인할 것이고,
캐시버퍼 체인에서 찾지 못한 블록들을 Multi Block I/O 방식으로 디스크 I/O Call을 수행할텐데, 이때 여러 개가 아닌 1개의 블록을 대상으로 수행할 떄에는 Single I/O 방식을 채택하게 될 것이다.
+) Full Scan 중 Chain이 발생한 로우를 읽을 떄에도 Single Block I/O 방식으로 읽는다.1.3.7. ✍️ Table Full Scan VS Index Range Scan
테이블에 저장된 데이터를 읽는 방식은 2가지다.
Table Full Scan : 테이블에 속한 블록 전체를 읽어 사용자가 원하는 데이터를 찾는 방식
Index Range Scan (인덱스를 이용한 테이블 액세스) : 인덱스에서 일정량을 스캔하여 얻은 ROWID로 테이블 레코드를 찾는 방식 (ROWID : 테이블 레코드가 디스크 상에서 어디 저장됐는지를 가리키는 정보)Table Full Scan 방식은 시퀀셜 액세스와 Multi Block I/O 방식으로 디스크 블록을 읽는다. 한 블록에 속한 모든 레코드를 한 번에 읽어들이고 캐시에서 못찾으면 동일 익스텐트 내 인접한 블록 수십 ~ 수백개를 한꺼번에 I/O 하는 매커니즘이다.
그러나, 큰 테이블 내에서 소량의 데이터를 검색할 때는 반드시 인덱스를 활용해야 효율적이다.
당연한 이야기지만, 예시를 들어보면 우리가 사과라는 과일을 살때 주변 마트나 시장을 검색하지 곧바로 트레이더스를 검색하진 않는다.
반면, Index Range Scan을 통한 테이블 액세스는 랜덤 액세스와 Single Block I/O 방식으로 디스크 블록을 읽는다. 소량의 데이터를 검색할 떄에는 적합하지만, 읽었던 블록을 반복해서 읽게 되는 비효율적인 문제가 존재하긴 한다.
[개인 견해]
그렇다면 소량이다, 대량이다가 되는 기준이 어떻게 될까?
이건 DB 테이블 내 적재된 데이터의 형태에 따라 상당히 달라진다고 생각한다.
텍스트 형태의 데이터이고 VARCHAR 단위가 3000 가까이 된다면, 이는 로우가 100개에 불과해도 대량 데이터라고 판단할 수 있을 것이다.쿼리 툴을 개발한다면, Table Full Scan 뿐만 아니라, 예상 Cardinality가 일정량을 넘어서는데도 인덱스로 테이블을 액세스하는 부분에 표시해주는 기능이 있다면 좋을 것이다.
1.3.8. ✍️ 캐시 탐색 메커니즘
DBMS의 버퍼캐시는 해시 구조로 관리된다.
버퍼캐시에서 블록을 찾을 때, 해시 알고리즘을 거쳐 버퍼 헤더를 찾고 거기서 얻은 포인터로 버퍼 블록을 액세스하는 방식을 사용한다.해시 구조의 특징은 다음과 같다.
1. 같은 입력 값은 항상 동일한 해시 체인에 연결된다.
2. 다른 입력 값이 동일한 해시 체인에도 연결될 수 있다.
3. 해시 체인 내에서는 정렬이 보장되지 않는다.🧨 캐시버퍼 체인 래치
래치란, 직렬화가 가능하도록 지원하는 메커니즘이다.
대량의 데이터를 읽을 때 모든 블록에 대해 해시 체인을 탐색한다.
DBA를 해시 함수에 입력하고 거기서 반환된 값으로 스캔해야 할 해시 체인을 찾는다.
이때, 다른 프로세스가 체인 구조를 변경한다면, 반환된 값으로 스캔을 못하기에 이를 방지하고자 해시 체인 래치가 존재하게 된다.캐시버퍼 체인뿐만 아니라, 버퍼 블록 자체에도 직렬화 메커니즘이 존재하는데, 바로 버퍼 Lock이다. 이런 직렬화 메커니즘에 의한 캐시 경합을 줄이기 위해선 SQL 튜닝을 통해 쿼리 일량(논리적 I/O)를 줄여야만 한다.