Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
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
Tags
more
Archives
Today
Total
관리 메뉴

seoyyyy-dev

1회차 SQL 처리 과정과 I/O 정리 본문

개발 독서 스터디/친절한 SQL 튜닝

1회차 SQL 처리 과정과 I/O 정리

seoyyyy 2025. 1. 4. 20:04

1. SQL 파싱과 최적화

1.1 SQL 이란?

    - Structured(구조적인) Query(질의) Language(언어) 의 줄임말

    - 집합적(set-based), 선언적(declarative) 인 질의언어

    - PL/SQL, SQL Server T-SQL 처럼 절차적(procedure) 프로그래밍을 구현할 수 있는 확장언어 제공


1.2 SQL 최적화

    - SQL 최적화 과정

       ① SQL 파싱: 사용자로부터 SQL을 전달 받으면 가장 먼저 SQL 파서가 파싱을 진행

            파싱 트리 생성: SQL 문을 이루는 개별 구성 요소 분석 및 파싱 트리 생성

             Syntax 체크: 문법적 오류 체크

             Semantic 체크: 의미상 오류 체크(존재하지 않는 테이블, 컬럼, 권한 체크)

        ② SQL 최적화: 옵티마이저(Optimizer)가 미리 수집한 시스템 및 오브젝트 통계 정보를 바탕으로 다양한 실행 경로를 생성 및 비교한 후 효율적인 하나 선택 

        ③ 로우 소스 생성: 로우 소스 생성기가 옵티마이저가 선택했던 실행경로를 확인하여 실제 실행 가능한 코드 OR 프로시저 형태로 포맷팅


1.3 SQL 옵티마이저

    - 사용자가 원하는 작업을 가장 효율적으로 수행할 수 있는 최적의 데이터 액세스 경로 선택, DBMS 핵심 엔진

    - 옵디마이저 최적화 단계

       사용자에게 전달받은 쿼리를 수행하는 데 후보군이 될만한 실행계획들을 찾아냄

        데이터 딕셔너리에 미리 수집해둔 오브젝트 통계 및 시스템 통계 정보를 이용해 각 실행계획의 예상비용 선정

        최저 비용의 실행계획 선택


1.4 실행계획(Execution Plan)과 비용(Cost)

    - 실행계획: SQL 실행경로 미리보기

      : 실행계획을 통해 작성한 SQL이 테이블 스캔인지 인덱스 스캔인지, 어떤 인덱스를 사용하는지 확인할 수 있다.

dbeaver에서 확인한 간단한 쿼리의 실행계획

- 옵티마이저가 실행계획을 선택하는 근거 예시(더보기 클릭!)

더보기
-- 테스트를 위해 테이블 생성
CREATE TABLE T
AS 
SELECT d.NO, e.*
FROM emp e, (SELECT rownum NO FROM dual CONNECT BY LEVEL <= 1000) d;
-- 옵티마이저가 최적의 경로를 탐색하는지 알기위해 인덱스 2개 생성
CREATE INDEX t_x01 ON t(deptno, no);
CREATE INDEX t_x02 ON t(deptno, job, no);

select *
from t
where deptno = 10
where no = 1;

 

→ 위의 select 쿼리 실행계획을 보면 옵티마이저는 아래와 같이Cost 2로 나타나는 t_x01 인덱스를 선택

→ t_x02 인덱스를 강제로 사용하도록 힌트를 주어 실행계획을 살펴보면 Cost가 5로 표시

→ Table Full Scan 하도록 강제로 힌트를 주어 실행계획을 살펴보면 cost가 29로 제일 높음

 

따라서 옵티마이저가 비용을 기반으로 t_x01 인덱스를 선택한 것을 알 수 있음

- 비용(Cost) : 쿼리를 수행하는 동안 발생할 것으로 예상되는 I/O 횟수 또는 예상 소요시간을 표현한 값

                     : 비용도 옵티마이저가 통계정보를 활용해 계산한 예상치이므로(실측치가 아님) 실제 수행 시 발생하는 I/O 또는 시간이 많이 차이날 수 있다. 

1.5 옵티마이저 힌트

- 옵티마이저의 한계로 인해 최선의 실행계획으로 선택되지 않았을 때 개발자가 직접 쿼리에 힌트를 주어 더 효율적인 액세스 경로를 찾아낼 수 있음

- 옵티마이저 사용방법

SELECT /*+ index(A 고객_PK) */
고객명, 연락처, 주소, 가입일시
FROM 고객 A
WHERE 고객 A
WHERE 고객ID = '00000008';

- 주의사항 

더보기

힌트안에 인자 나열 시 ','(콤마) 사용이 가능하지만 힌트와 힌트 사이에 사용하게 되면 첫번째 힌트만 유효하게 됨

/*+ index(A A_X01) INDEX (B, B_X03) */ => 유효
/*+ index(A A_X01), INDEX (B B_X03) */ => 첫 번째 힌트만 유효

테이블 지정 시 스키마명까지 명시하면 안되며 ALIAS 지정 시 힌트에도 반드시 ALIAS로 사용해주어야함

SELECT /*+ FULL(SCOTT.EMP) */ * FROM EMP; => 무효 'FULL(EMP)'로 지정해야 함
SELECT /*+ FULL(EMP) FROM*/ * FROM EMP E; => 무효'FULL(E)'로 지정해야 함

또한 힌트에 컬럼 일부만 지정하여 해당 컬럼이 선두인 인덱스 중에 알아서 선택되도록 할 수 있으며 이경우 조인 방식과 순서, 고객테이블 액세스 방식은 옵티마이저가 알아서 판단한다.

SELECT /*+ INDEX(A (주문일자)) */ A.주문번호, A.주문금액, B.고객명, B.연락처, B.주소
FROM 주문 A, 고객 B
WHERE A.주문일자 = :ORD_DT
  AND A.고객ID = B.고객ID

옵티마이저의 자율적 판단에 맡기기 힘든 중대한 시스템인 경우엔 힌트 사용시 빈틈없이 기술하는 것이 좋다.

SELECT /*+ LEADING(A) USE_NL(B) INDEX(A (주문일자)) INDEX(B 고객_PK) */
       A.주문번호, A.주문금액, B.고객명, B.연락처, B.주소
FROM 주문 A, 고객 B
WHERE A.주문일자 = :ORD_DT
  AND A.고객ID = B.고객ID;

 

자주 사용하는 힌트 목록(더보기 클릭!)

더보기
분류 힌트 설명
최적화목표 ALL_ROWS 전체 처리속도 최적화
FIRST_ROWS(N) 최초 N건 응답속도 최적화
액세스 방식 FULL Table Full Scan으로 유도
INDEX Index Scan으로 유도
INDEX_DESC Index를 역순으로 스캔하도록 유도
INDEX_FFS Index Fast Full Scan으로 유도
INDEX_SS Index Skip Scan으로 유도
조인순서 ORDERED FROM 절에 나열된 순서대로 조인
LEADING LEADING 힌트 괄호에 기술한 순서대로 조인
(예) LEADING(T1 T2)
SWAP_JOIN_INPUTS 해시 조인 시, BUILD INPUT을 명시적으로 선택
(예) SWAP_JOIN_INPUTS(T1)
조인방식 USE_NL NL 조인으로 유도
USE_MERGE 소트 머지 조인으로 유도
USE_HASH 해시 조인으로 유도
NL_SJ NL 세미조인으로 유도
MERGE_SJ 소트 머지 세미조인으로 유도
HASH_SJ 해시 세미조인으로 유도
서브쿼리
팩토링
MATERIALIZE WITH 문으로 정의한 집합을 물리적으로 생성하도록 유도
예) WITH /*+  MATERIALIZE */ T AS (SELECT ....)
INLINE WITH 문으로 정의한 집합을 물리적으로 생성하지 않고 INLINE으로 처리하도록 유도
예) WITH /*+ INLINE */ T AS (SELECT ... )
쿼리 변환 MERGE 뷰 머징 유도
NO_MERGE 뷰 머징 방지
UNNEST 서브쿼리 Unnesting 유도
NO_UNNEST 서브쿼리 Unnesting 방지
PUSH_PRED 조인조건 Pushdown 유도
NO_PUSH_PRED 조인조건 Pushdown 방지
USE_CONCAT OR 또는 IN-List 조건을 OR-Expansion으로 유도
NO_EXPAND OR 또는 IN-List 조건에 대한 OR-Expansion 방지
병렬처리 PARALLEL 테이블 스캔 또는 DML을 병렬방식으로 처리하도록 유도
예) PARALLEL (T1 2) PARALLEL(T2 2)
PARALLEL_INDEX 인덱스 스캔을 병렬방식으로 처리하도록 유도
PQ_DISTRIBUTE 병렬 수행 시 데이터 분배 방식 결정
예) PQ_DISTRIBUTE(T1 HASH HASH)
기타 APPEND Direct-Path Insert 유도
DRIVING_SITE DB Link Remote 쿼리에 대한 최적화 및 실행 주체 지정
(Local 또는 Remote)
PUSH_SUBQ 서브쿼리를 가급적 빨리 필터링하도록 유도
NO_PUSH_SUBQ 서브쿼리를 가급적 늦게 필터링하도록 유도

 


2. SQL 공유 및 재사용

2.1 소프트 파싱 VS 하드파싱

- 라이브러리 캐시(Library Cache): SQL 파싱, 최적화, 로우 생성 과정을 거쳐 생성한 내부 프로시저를 반복 재사용할 수 있도록 캐싱해두는 메모리 공간, SGA의 구성요소

- SGA (System Global Area): 서버프로세스와 백그라운드 프로세스가 공통으로 액세스하는 데이터와 제어구조를 캐싱하는 메모리 공간

그림판으로 허접하게 그린 SGA 구성요소

- 소프트파싱(Soft Parsing)

   : 사용자가 SQL문을 전달하면 DBMS가 SQL을 파싱한 후 SQL이 라이브러리 캐시에 존재하는지 확인하여 캐시에 존재하면 바로 실행단계로 넘어가는 것

- 하드파싱(Hard Parsing)

 : 캐시에 존재하지 않아 최적화 및 로우 소스 생성 단계까지 모두 거치게 되는 것

- 옵티마이저가 최적화하는데 사용하는 정보

   ● 테이블, 컬럼, 인덱스 구조에 관한 기본 정보

   ● 오브젝트 통계: 테이블 통계, 인덱스 통계, (히스토그램을 포함한) 컬럼 통계

   ● 시스템 통계: CPU 속도, Single Block I/O 속도, Multiblock I/O 속도 등

   ● 옵티마이저 관련 파라미터

   => 이 정보들을 사용하여 옵티마이징 할 때마다 매번 실행경로를 계산하면 CPU가 많이 소비되고 비효율적이므로 라이브러리 캐시가 필요


2.2 바인드 변수의 중요성

- 이름없는 SQL 문제

   - 사용자 정의 함수(Function)/프로시저, 트리거, 패키지 

   : 생성 시 이름을 갖고 컴파일한 상태로 딕셔너리에 저장되며 사용자가 삭제하지 않는 한 영구 보관된다.

   : 실행할 때 라이브러리 캐시에 적재함으로써 여러 사용자가 공유하면서 재사용함

   - SQL

   : 이름이 따로 없으며 전체 텍스트가 이름 역할을 함, 딕셔너리 저장 X

   : 처음 실행 시 최적화 과정을 거쳐 동적으로 생성한 내부 프로시저를 라이브러리 캐시에 적재함으로써 여러 사용자가 공유 및 재사용

   : 캐시공간 부족 시 버려졌다가 다음 실행 시 다시 최적화 과정을 거쳐 캐시에 적재

- 공유 가능 SQL

   - 라이브러리 캐시에서 SQL을 찾기 위해서는 키 값이 'SQL 문 그 자체' 이므로 조건까지 똑같아야 같은 쿼리로 인식함

   - 아래 쿼리는 의미적으로는 같지만 모두 다른 쿼리라고 인식해 각각 최적화되며 라이브러리 캐시에 별도로 저장

SELECT * FROM EMP WHERE EMPNO = 7900;
select * from EMP where EMPNO = 7900;
select * from emp where empno = 7900;
select * from emp where empno = 7900 ;
select * from emp where empno = 7900   ;
select * from scott.emp where empno = 7900;
select /* comment */ * from emp where empno = 7900;
select /*+ first_rows */ * from emp where empno = 7900;

- JAVA에서 SQL문 작성 시 아래 쿼리와 같이 실행하게 되면 하드파싱으로 진행될 것임

Public void login(String login_id) throws Exception {
	String SQLStmt = "SELECT * FROM CUSTOMER WHERE LOGIN_ID = '" + login_id + "'";
	Statement st = con.createStatement();
	ResultSet rs = st.executeQuery(SQLStmt);
	if(rs.next()){
		...
	}
	rs.close();
	st.close();
}

바인드 변수를 사용해주면 소프트 파싱이 가능해짐

/** 바인드 변수 사용 시 로그인 아이디가 달라져도 라이브러리 캐시에는**/
/** SELECT * FROM CUSTOMER WHERE LOGIN_ID = :1 하나만 조회된다. **/
Public void login(String login_id) throws Exception {
	String SQLStmt = "SELECT * FROM CUSTOMER WHERE LOGIN_ID = ?";
	Statement st = con.createStatement();
	st.setString(1, login_id);
	ResultSet rs = st.executeQuery(SQLStmt);
	if(rs.next()){
		...
	}
	rs.close();
	st.close();
}

3. 데이터 저장 구조 및 I/O 메커니즘

3.1 SQL이 느린 이유

- 프로세스(Process) 생명 주기

  생성(new) 승인(admitted)                                                         exit  →   종료(terminate)

                                   준비(ready)  ←  interrupted / dispatched →  실행(running)

            I/O 완료or 이벤트 완료   ←      대기(waiting)      ← ↙ I/O 요청 or 이벤트 대기

- 실행중인 프로세스는 interrupt에 의해 수시로 실행 준비(runnable Queue)로 전환했다가 다시 실행상태로 전환

- interrupt없이 열심히 일하던 프로세스도 디스크에서 데이터를 읽어야 할 땐 CPU를 OS에 반환하고 잠시 수면(waiting) 상태에서 I/O가 완료되기를 기다림(Wait Queue)에서 잠을 잠 => I/O가 많으면 성능이 느림


3.2 데이터베이스 저장구조

그림판으로 허접하게 그려본 테이블 스페이스 구조

● 블록: 데이터를 읽고 쓰는 단위

   - 한블록 및 하나의 익스텐트는 하나의 테이블이 독접한다.

● 익스텐트: 공간을 확장하는 단위, 연속된 블록 집합

   - 테이블이나 인덱스에 데이터를 입력하다 공간 부족 시 테이블 스페이스로부터 익스텐트를 추가로 할당받는다. 

   - 익스텐트 단위로 확장하지만 실제로 데이터를 저장하는 공간은 데이터 블록이다.

● 세그먼트: 데이터 저장공간이 필요한 오브젝트(테이블, 인덱스, 파티션, LOB 등)

   - 여러 익스텐트로 구성됨

   - LOB 컬럼은 그 자체가 하나의 세그먼트를 구성하므로 자신이 속한 테이블과 다른 별도 공간에 값 저장

● 테이블 스페이스: 세그먼트를 담는 콘테이너

● 데이터 파일: 디스크 상의 물리적인 OS 파일

   - 세그먼트에 할당된 모든 익스텐트가 같은 데이터파일에 위치하지 않을 수 있다.(파일 경합을 줄이기 위해 DBMS가 데이터를 가능한 여러 데이터파일로 분산 저장하기 때문)

도저히 힘들어서 파워포인트로 그린 데이터파일 구조 구조

   - 익스텐트 내 블록은 서로 인접한 연속된 공간이지만, 익스텐트끼리는 연속된 공간이 아님 

   - 세그먼트에 할당된 익스텐트 목록 조회법(오라클)

SELECT SEGMENT_TYPE, TABLESPACE_NAME, EXTENT_ID, FILE_ID, BLOCK_ID, BLOCKS
FROM DBA_EXTENTS
WHERE OWNER = 'SEOYYYY'
  AND SEGMENT_NAME = 'T' -- 아까 만든 테이블
ORDER BY EXTENT_ID;

3번 익스텐트는 888번, 4번 익스텐트는 10,240번 블록부터 시작한다.(직전 익스텐트끼리 인접하지 않음)

DBA(Data Block Address): 디스크 상에서 몇 번 데이터 파일의 몇 번째 블록인지 나타내는 고유한 주소

  - 인덱스로 테이블 레코드를 읽을 때 인덱스 ROWID를 이용해야 하는데 ROWID는 DBA + 로우 번호(블록 내 순번)으로 구성

  - 테이블 스캔 시 테이블 세그먼트 헤더에 저장된 익스텐트 맵을 이용, 익스텐트 맴을 통해 각 익스텐트의 첫번째 블록 DBA를 알 수 있다.


3.3 블록 단위 I/O

- 테이블의 데이터 I/O 단위는 블록이므로 특정 레코드를 읽고자 할 때 무조건 해당 블록을 통째로 읽어야 한다. 

- 오라클은 기본 블록 하나당 8KB 이므로 1Byte만 읽고 싶어도 기본 8KB를 읽게된다.

- 테이블뿐 아니라 인덱스도 블록단위로 데이터를 읽고 쓴다.


3.4 시퀀셜 액세스 VS 랜덤 액세스

① 시퀀셜(Sequential) 액세스: 논리적 또는 물리적으로 연결된 순서에 따라 차례대로 블록을 읽는 방식

  : 인덱스 리프 블록에 명시된 앞뒤를 가리키는 주소를 따라서 순차적(앞 또는 뒤로)으로 스캔하는 방식

  : 테이블 블록간에는 서로 논리적인 연결고리를 갖고 있지 않으며 세그먼트에 할당된 익스텐트 목록을 세그먼트 헤더에 맵으로 관리한다. 익스텐트 맵은 각 익스텐트의 첫 번째 블록 주소 값을 갖는다.(블록은 연속된 공간으로 첫번째만 알면 됨)

각 익스텐트의 첫번째 블록 뒤에 연속해서 저장된 블록을 순서대로 읽으면 이것이 Full Table Scan

② 랜덤(Random) 액세스: 논리적, 물리적 순서를 따르지 않고 레코드 하나를 읽기 위해 한 블록씩 접근(=touch)하는 방식


3.5 논리적 I/O VS 물리적 I/O

- DB 버퍼캐시

   : 디스크에서 읽은 데이터 블록을 캐싱해 둠으로써 같은 블록에 대한 반복적인 I/O Call 을 줄이는데 목적이 있다.

   : 라이브러리 캐시는 '코드 캐시', DB 버퍼 캐시는 '데이터 캐시'

   : SGA의 가장 중요한 구성요소 중 하나

   : 데이터 블록을 읽을 때 항상 버퍼 캐시부터 탐색 후 블록이 존재하면 캐시의 블록을 사용, 존재하지 않으면 데이터 파일에 물리적으로 액세스(I/O Call)를 거쳐 DB 버퍼 캐시에 적재한다. 

   : 버퍼캐시는 공유 메모리 영역이므로 같은 블록을 읽는 다른 프로세스도 득을 본다.

3번째 행이 DB 버퍼캐시 사이즈

- 논리적 블록 I/O : SQL을 처리하는 과정에서 발생한 총 블록 I/O (메모리 I/O + Direct Path I/O)

  : DB 버퍼 캐시에서 블록을 읽은 횟수와 일치한다.

- 물리적 블록 I/O : 디스크에서 발생한 총 블록 I/O

  : SQL 처리 도중 블록을 버퍼캐시에서 못 찾았을 때 디스크를 액세스하므로 물리적 I/O가 발생한다.

  : 메모리 I/O는 전기적 신호, 디스크 I/O는 액세스 암(Arm)을 통해 물리적 작용이 일어나 상당히 느리다. 디스크 경합이 심할땐 더느림

  : 연속 실행 시 DB버퍼캐시에서 해당 테이블 블록의 점유율이 높아져 물리적 I/O 횟수가 줄어든다.(한참 뒤 실행하면 반대로 물리적 I/O 횟수가 증가함)

- 버퍼캐시 히트율(Buffer Cache Hit Ratio)

  : 읽은 전체 블록 중 물리적인 디스크 I/O를 수반하지 않고 곧바로 메모리에서 찾은 비율

  : 온라인 트랜잭션을 주로 처리하는 애플리케이션이라면 시스템 레벨에서 평균 99% 히트율을 달성해야 한다.

BCHR = ( 캐시에서 곧바로 찾은 블록 수 / 총 읽은 블록 수 ) X 100
	 = ( ( 논리적 I/O - 물리적 I/O ) / 논리적 I/O ) X 100
	 = ( 1 - (물리적I/O) / (논리적 I/O) ) X 100

물리적 I/O = 논리적 I/O X (100% - BCHR)

  => 물리적 I/O가 성능을 결정하지만 실제 SQL 성능 향상을 위해 논리적 I/O를 줄여야 한다.(물리적 I/O는 통제 불가능한 외생변수)

  => BCHR이 평균 70%일 때 논리적 I/O가 10,000개면 물리적 I/O는 대략 3,000개쯤 발생할 것으로 예상된다.

  => 논리적 I/O를 1,000개로 줄이면 물리적 I/O도 300개로 감소하고 성능도 열배 향상된다.

- 논리적 I/O를 줄이는 방법?

  : SQL을 튜닝해서 읽는 총 블록 개수를 줄이면 된다.

 

SQL 트레이스를 통해 수집한 Call 통계 정보에서 BCHR 구하기

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00      0.001          0          0          0           0
Execute      1     0.010      0.006          0          0          0           0
Fetch        2   138.680   1746.630     601458    1351677      12367           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4   138.690       0.00     601458    1351677      12367           1

Query + Current = 논리적 I/O(물리적 I/O 수가 포함되어있음)                          

1,351,677+ 12,367 = 1,364,044                                                                                   

disk = 물리적 I/O

601,458

 

BCHR =  ( 1 - (Disk / ( Query + Current ) ) ) x 100

           =  ( 1 - (601,458 / 1,351,677 + 12,367 ) ) ) x 100 

           = 55.9 %

 

- BCHR이 SQL 성능을 좌우하지만 BCHR이 높다고 효율적인 SQL을 의미하는 건 아니다.

- 같은 블록을 비효율적으로 반복해서 읽으면 BCHR이 높아질 수 있다.


3.6 Single Block I/O VS Multiblock I/O

- Single Block I/O

: 캐시에서 찾지 못한 데이터 블록을 I/O Call을 통해 한번에 한 블록 씩 요청해 DB 버퍼캐시에 적재하는 방식

: 인덱스를 이용할 때 기본적으로 인덱스와 테이블 블록 모두 이 방식을 사용

: Single Block I/O 대상 오퍼레이션

    - 인덱스 루트 블록을 읽을 때

    - 인덱스 루트 블록에서 얻은 주소 정보로 브랜치 블록을 읽을 때

    - 인덱스 브랜치 블록에서 얻은 주소 정보로 리프 블록을 읽을 때

    - 인덱스 리프 블록에서 얻은 주소 정보로 테이블 블록을 읽을 때

- Multiblock I/O

: 캐시에서 찾지 못한 특정 블록을 읽으려고 I/O Call 할 때 디스크 상에 그 블록과 '인접한' 블록들을 한꺼번에 읽어 캐시에 미리 적재하는 방식

: 같은 익스텐트 내의 '인접한' 블록만 읽기 때문에 Multiblock으로 읽더라도 다른 익스텐트까지 같이 담지 못한다.

: Multiblock I/O  대상 오퍼레이션

    - 인덱스를 이용하지 않고 테이블 전체를 스캔할 때

    - 대용량 테이블 Full Scan 시 유리

: 테이블이 클수록 Multiblock I/O 단위도 크면 좋다. 프로세스가 대기하며 잠자는 횟수를 줄여주기 때문

: DBMS 블록사이즈가 얼마이건 OS단에서는 보통 1MB 단위로 I/O를 수행한다. 테이블 전체 블록 읽을 때는 손수레에 한번에 담을 수 있는 만큼 최대한 많이 담아야 유리하다.

- 한번에 읽을 수 있는 multiblock 수

OS 레벨 I/O 단위 1MB, 오라클 레벨 I/O 단위 8KB 이므로 (8KB X 128) = 1MB로 최대한 많이 담도록 늘려줌


3.7 Table Full Scan VS Index Range Scan

- Table Full Scan: 테이블 전체를 스캔해서 읽는 방식

  : 테이블에 속한 블록 '전체를' 읽어 사용자가 원하는 데이터를 찾는 방식

  : 한번에 많은 데이터를 처리하는 집계용 SQL에서 유리함

  : 시퀀셜 액세스(순차적으로 읽음), Multiblock I/O(한번에 인접한 여러 블록 읽음)

- Index Range Scan: 인덱스를 이용한 테이블 엑세스 

   : 인덱스에서 '일정량'을 스캔하면서 얻은 ROWID로 테이블 레코드를 찾아가는 방식

   : ROWID는 디스크상에 테이블 레코드가 어디에 저장됐는지 가리키는 위치

   : 소량의 데이터를 검색할 때 유리함

   : 많은 데이터를 읽으면 물리적인 블록 I/O 뿐만 아니라 논리적인 블록 I/O도 불리하다.(읽었던 블록을 반복해서 읽게될 수 있음)

   : 랜덤 액세스, Single Block I/O(한번에 하나의 블록 읽음)

 

=> 인덱스가 항상 옳은 것은 아니다. 조회해야하는 데이터가 많으면 Table Full Scan, 소량이면 Index Range Scan!


3.8 캐시 탐색 매커니즘

- 버퍼 캐시 탐색과정을 거치는 오퍼레이션

● 인덱스 루트 블록을 읽을 때

인덱스 루트 블록에서 얻은 주소 정보로 브랜치 블록을 읽을 때

인덱스 브랜치 블록에서 얻은 주소 정보로 테이블 블록을 읽을 때

테이블 블록을 Full Scan 할 때

- 버퍼 캐시에서 블록을 찾을 때 해시 알고리즘으로 버퍼 헤더를 찾고 거기서 얻은 포인터(pointer)로 버퍼 블록을 액세스하는 방식으로 사용한다.

버퍼 캐시 구조(해시함수는 실제로 더 정교하지만 책에서는 이해하기 쉽도록 Mod 함수로 표현함

- 캐시 탐색 예시

① 20번 블록을 찾고자 할 때 Mod(20, 5)  => 20 % 5 = 0 이므로 해시값 0번의 체인을 읽는다.

② 27번 블록을 찾고자 할 때 Mod(27, 5) => 27 % 5 = 2 이므로 2번 체인을 탐색했는데 없다면 디스크에서 읽어와 세번째(2번) 체인에 연결시킨 후 읽어온다.

- 해시구조의 특징

   ● 같은 입력 값은 항상 동일한 해시 체인(=버킷)에 연결된다.

   ● 다른 입력 값(예를 들어, 4와 9)이 동일한 해시 체인(=버킷)에 연결될 수 있다.

   ● 해시 체인 내에서는 정렬이 보장되지 않는다.

 

- 메모리 공유자원에 대한 액세스 직렬화

: 버퍼캐시는 SGA 구성요소이므로 버퍼캐시에 캐싱된 버퍼블록은 모두 공유자원이다. => 누구나 접근가능

: 하나의 버퍼블록을 두 개 이상의 프로세스가 '동시에' 접근하려고 할 때 문제가 발생한다. => 동시 접근 시 정합성 문제

: 이를 위해 직렬화(serialization) 메커니즘이 필요. => 래치(Latch), 버퍼 Lock으로 직렬화 매커니즘 구현

 

- 캐시버퍼 체인 래치

: 해시 체인을 스캔하는 동안 다른 프로세스가 체인 구조를 변경하는 일이 생기지 않도록 자물쇠(Latch)를 걸고 자물쇠를 열 수 있는 (key)를 획득한 프로세스만이 체인으로 진입할 수 있다.

- 버퍼 Lock

 : 읽고자하는 블록을 찾았으면 곧바로 캐시버퍼 체인 래치를 해제해야 한다. 그래야 대기하고 있던 다른 프로세스들이 작업을 재개할 수 있다.

 : 래치를 해제한 상태로 작업중인 버퍼 Lock에 후행 프로세스가 동시에 접근해서 데이터를 읽고 쓰면 정합성 문제가 발생할 수 있으므로 버퍼에도 Lock을 설정한다.(로우 lock과는 다름, 로우 lock을 설정하는 것 자체도 블록을 변경하는 작업)

 

데이터 작업시 직렬화 매커니즘 간단 순서

 : 캐시버퍼 체인 래치를 획득 => 버퍼 Lock 설정(먼저 버퍼에 Exclusive Lock이 걸려있으면 이벤트 대기하고 래치 해제) => 캐시버퍼 체인 래치 해제 => 읽기/쓰기 작업 완료(버퍼 캐시에 없으면 디스크 I/O 발생) => 캐시버퍼 체인 래치 획득 => 버퍼 Lock 해제 => 캐시버퍼 체인 래치 해제

'개발 독서 스터디 > 친절한 SQL 튜닝' 카테고리의 다른 글

4회차 조인 튜닝  (0) 2025.02.25
3회차 인덱스 튜닝 - 3  (0) 2025.02.08
3회차 인덱스 튜닝 - 2  (0) 2025.02.08
3회차 인덱스 튜닝 - 1  (1) 2025.02.08
2회차 인덱스 기본  (0) 2025.01.21