1.1 SQL 파싱과 최적화
-
SQL : Structured Query Language
- SQL 최적화 : DBMS 내부에서 프로시저를 작성하고 컴파일해서 실행 가능한 상태로 만드는 과정
- SQL 문장의 문법과 의미상 오류가 없는지 파싱함.
- 미리 수집한 통계정보를 바탕으로 다양한 실행 경로를 생성해 가장 효율적인 길을 선택
- 가장 효율적인 길을 이용해 실제 로우 소스를 생성
- SQL 옵티마이저 : 위 두 번째 단계에서 가장 효율적인 길을 계산하는 DBMS의 핵심 엔진
- 네비게이션처럼 미리 이동 경로를 하나씩 확인하는 방식임.
- 그리고 결정한 이동 경로를 ‘실행계획’이라는 기능을 이용해서 확인할 수 있음
- SQL 문장으로 실행계획을 ON 시켜 확인할 수도 있고, 보통 DB 툴에서 바로 확인할 수 있음.
- 네비게이션도 항상 가장 빠른 길을 보여주는 것이 아닌 것처럼, SQL 옵티마이저가 정한 실행계획이 최선이 아닐 수 있음. 이럴 때는 힌트를 제공하여 실행계획을 변경해야 함.
-
힌트 : 옵티마이저에게 자신이 원하는 방향으로 실행계획을 세우도록 힌트를 주는 방법
-- EMP 테이블에 대해서 해당 인덱스를 사용하라는 간단한 힌트 -- 테이블을 명시할 때는 스키마를 명시하면 안 됨. 반드시 테이블명만 지정 SELECT /*+ INDEX(EMP 인덱스명) */ * FROM ... -- 힌트를 여러 개 사용할 때는 콤마(,)를 사용하면 안 됨. 가장 앞에것만 적용돼버림 SELECT /*+ INDEX(EMP 인덱스명) INDEX(DEPT 인덱스명) */ * FROM ... -- 테이블에 ALIAS를 지정했다면 힌트에도 반드시 지정한 ALIAS로 테이블을 표현해야 함 SELECT /*+ INDEX(A 인덱스명) */ * FROM DEPT A ...
1.2 SQL 공유 및 재사용
- SQL 문장을 한번 수행하면 최적화 후에 결정한 실행 계획을 재사용하기 위해 캐싱한다.
- SGA(System Global Area)라는 메모리 공간에 존재하는 Library Cache에 캐싱
- 캐싱을 하는 이유는 실행계획을 결정하기 위한 계산이 부담이 됨.
- 테이블, 컬럼, 인덱스 구조도 파악해야 하고
- 테이블과 인덱스, 그리고 컬럼 통계를 확인하고
- Single Block I/O 나 MultiBlock I/O가 빠른지도 확인해야 하고 (밑에 설명)
- 어떤 인덱스를 사용해야 할지 등등
- 그래서 캐시에 존재하지 않아 최적화의 모든 단계를 수행하는 것을 하드 파싱이라고 표현
- 반대로 Library Cache에서 발견해서 바로 실행단계 넘어가는 것을 소프트 파싱이라고 표현
- Library Cache에서 해당 SQL을 캐싱했는지 찾으려면 Map처럼 Key가 필요
- SQL 문장은 따로 Key가 없으므로 SQL 문장 자체를 Key로 사용함.
- SQL 문장이 조금만 달라져도 하드 파싱을 수행. 이 말은 즉 같은 SQL 문장이지만 조건절의 변수 값이 달라도 하드 파싱
-
따라서 바인드 변수를 사용하여 SQL 문장을 작성해야 하는 이유임
SELECT * FROM DEPT WHERE DEPT_ID = :1
1.3 데이터 저장 구조 및 I/O 메커니즘
-
데이터베이스 저장 구조 (큰 순서대로 나열)
-
테이블스페이스 : 세그먼트를 담는 컨테이너
-
세그먼트 : 테이블, 인덱스, 파티션처럼 데이터 저장공간이 필요한 오브젝트. 세그먼트는 여러 익스텐트로 구성됨.
-
익스텐트 : 세그먼트(오브젝트)의 공간이 부족할 경우, 공간을 확장하는 단위. 만약 테이블에 데이터가 많이 쌓여서 공간이 부족할 경우 익스텐트를 추가로 할당받음.
-
블록 : 사용자가 입력한 레코드를 실제로 저장하는 공간. 읽고 쓰는 단위임
- 특정 레코드를 하나만 가지고 오고 싶어도, 블록 단위로 읽기 때문에 블록을 통째로 읽음
- 블록의 디폴트 크기는 8KB
-
- 블록을 읽는 방법에는 시퀀셜 액세스와 랜덤 액세스과 있음
- 시퀀셜 액세스 : 논리적 또는 물리적으로 연결된 순서에 따라 차례대로 블록을 읽는 방식. 즉 Full Scan
- 랜덤 액세스 : 특정한 랜덤 방식으로 블록을 읽는 방식
- 캐시
- 위에서 설명했지만, 실행계획은 SGA(System Global Area)라는 메모리 공간에 존재하는 Library Cache에 캐싱함. 이 외에도 함수/프로시저 등도 캐싱하기 때문에 ‘코드 캐시’ 라고도 함.
- 실제 쿼리를 수행해서 디스크 I/O에서 가지고 온 데이터 자체도 캐시에 저장하는데, SGA라는 메모리 공간에 존재하는 DB 버퍼 캐시에 캐싱함. 데이터를 캐시 하므로 ‘데이터 캐시’라고도 함.
- 1.3.5 논리적 I/O와 물리적 I/O (세 번째 읽고 있지만, 여전히 이해가 안 됨..)
- 논리적 I/O : DB 버퍼 캐시에서 가지고 온 블록
- 물리적 I/O : 디스크에서 가지고 온 블록. 당연 디스크에서 가지고 오므로 1000배 정도 느림
- 이 다음 설명부터 이해되지 않음..
- SQL이 느린 이유는 거의 무조건 디스크 I/O 때문
- SQL 튜닝 : 논리적 I/O를 줄임으로써 물리적 I/O를 줄이는 것
- Single vs Multi Block I/O
- Single Block I/O : 한 번에 한 블록씩 요청해서 버퍼 캐시에 적재하는 방식
- 디폴트로 인덱스와 테이블 블록 모두 이 방식을 사용
- 또한 인덱스는 소량 데이터를 읽을 때 주로 사용하므로 이 방식이 효율적
- Multi Block I/O : 한 번에 여러 개 블록을 버퍼 캐시에 적재하는 방식
- 대용량 테이블을 Full Scan 해야 한다면 이 방식이 효율적
- 일반적으로 1MB. DBMS에서는 더 크게 설정은 할 수 있어도 OS에서 최대 지원하는 I/O 단위가 1MB
- 블록을 한 번에 가지고 올 때, 한 익스텐트에서만 동작함. 다른 익스텐트로 넘어가서까지 한 번에 가지고 오지는 않음
- Single Block I/O : 한 번에 한 블록씩 요청해서 버퍼 캐시에 적재하는 방식
- Scan 방식은 아주 크게 두 가지로 나눌 수 있음
- Table Full Scan : 테이블에 속한 블록 전체를 읽는 방식
- 이 방식이 항상 나쁜 것은 아님
- 집계용 SQL 이나 배치성 SQL처럼 많은 데이터를 가지고 와야 한다면 이 방식이 빠를 수 있음.
- 왜냐하면 Multi Block 방식으로 여러 블록을 한 번에 가지고 오기 때문
- Index Range Scan : 인덱스를 이용하여 읽는 방식
- 소량의 데이터를 읽을 때 좋은 방식
- Table Full Scan : 테이블에 속한 블록 전체를 읽는 방식
- 버퍼 캐시 탐색 방법
- 해시함수를 사용해서 나온 결과로 링크드 리스트 헤더를 찾음
- 찾으려고 하는 블록이 나올 때까지 리스트 탐색
- 발견하면 끝
- 없다면 디스크로부터 읽어서 리스트에 추가
- 버퍼는 동시에 사용할 수 없어 락이 필요해서 Latch라는 방식으로 각각의 링크드 리스트마다 락을 걸음
- 버퍼 캐시를 탐색하는 것도 락에 의해서 느릴 수 있으므로, 버퍼 캐시조차 접근하는 것을 줄여야 한다. 즉, 논리적 I/O를 줄여야 한다.