SQLP 3-1-2 SQL Parsing, DB Call, Block I/O
in Devlopment on SQLP
3과목 SQL 고급 활용 및 Tuning
1장 Architecture 기반 Tuning
1.2 SQL Parsing
SQL Parsing 과정
- 사용자 입력 SQL
- Semantic 검사 : 문법적 오류, 객체 존재 여부
- Library Cache에서 검색 : Hash구조로 관리. SQL의 Hash값의 Bucket을 찾아봄
- Soft Parsing : Cache에서 찾아서 바로 실행단계로 넘어감
- Hard Parsing : Cache에서 찾지 못해서 최적화 과정을 거치고 실행단계로 넘어감
- 찾지 못했을 경우 최적화 수행
- 최적화 결과를 Library Cache의 Hash Bucket chain에 연결
- 해당 Execution Plan을 실행
- Optimizer : 사용자 SQL을 가장 빠르고 효율적인 처리경로를 선택해 주는 DBMS의 핵심엔진
####C aching 된 SQL 공유
- 공유조건 : 그냥 무조건 같은 SQL문이어야 한다.
- 공백, 줄바꿈이 다른 경우 : X
- 대소문자가 다른 경우 : X
- 주석이 다른 경우 : X
- OWNER명시 여부 (EMP 와 SCOTT.EMP) : X
- Optimizer Hint 여부 : X
- WHERE 절의 literal값이 다른 경우 : X
솔직히 1 ~ 5는 각 부서별 SQL표준을 정하면 되는데 6의 경우에는 Bind Variable을 사용할 수 밖에 없다.
Bind Variable을 사용하면 SQL 재사용율이 좋아진다.
하지만 Bind Variable을 사용하면 실행계획 생성시 통계정보를 활용하지 못한다.
(당연한 얘기다. 어떤 값이 들어올지 모르기 때문에 그냥 균등하다고 생각하고 실행계획을 작성한다.)
- Bind Variable을 사용하지 않는게 좋은 경우
- DW, OLAP 환경에서의 Long Running Query : Parsing 소유시간에 비해 Execution 시간이 훨씬 길며, Parsing도 자주 일어나지 않는다.
- WHERE절의 칼럼 Distinct 값이 적을 경우 : 그만큼 분포가 균일하지 않기 때문에 Histogram을 활용하는게 유리하다.
- Bind Variable Peeking (Sniffing)
- SQL이 처음 실행될때 Bind Variable 값을 살짝 훔쳐보고 Execution Plan을 작성
- 뒤에 어떻게 분포가 바뀔지 모르므로 상당히 위험한 기능
- 왠만해서는 해당 기능을 비활성화 하는게 좋음
ALTER SYSTEM SET "_optim_peek_user_binds" = FALSE;
Static SQL, Dynamic SQL
- Static SQL : code 사이에 SQL문을 직접 기술 (Pro*C, Power Builder, SQLJ 등… ). Compile 단계에서 SQL 구문체크가 가능
Dynamic SQL : String 타입 변수에 SQL문을 저장 (현재 대부분의 경우)
- Application Cursor Caching
- SQL문을 한번 Parsing한 후 Bind Variable 값만 바꿔가면서 반복적으로 수행
- Java에서
.setImplicitCachingEnabled(true)
로 한다던지 Statement를 닫지 않고 재사용하는 방법 - Parse Call 한번에 Execute Call이 여러번 일어남 (일반적인 경우는 Parse, Execute Call의 수가 같음)
1.3 Database Call과 네트워크 부하
Call의 종류
- SQL Cursor 작업 요청에 따른 구분
- Parse Call : SQL Parsing을 요청
- Execute Call : SQL 실행을 요청
- Fetch Call : SELECT문의 결과 데이터 전송을 요청
- Call 발생 위치에 따른 구분
- User Call : DBMS외부로부터 요청
- Recursive Call : DBMS내부에서 발생하는 Call
- SQL Parsing 과정에서 발생하는 데이터 딕셔너리 조회
- 사용자 정의 함수/프로시저 내에서의 SQL수행
User Call과 성능
User Call은 시스템 확장성을 떨어뜨리는 가장 큰 요인 중 하나이므로 최소화 하려는 노력이 중요하다.
1. One SQL 구현 : Loop Query를 해소
- 1번의 Call로 해결되는 Query는 5번의 Call로 해결되는 Query에 비해 5배의 확장성을 갖는 것이다.
2. Array Processing : Array 단위 Fetch, Bulk Insert/Update/Delete
- INSERT 할때 Array에 계속 담다가 1,000건이 쌓일 때마다
executeBatch()
실행 (Java) - SELECT 할때
.setFetchSize(1000)
으로 1,000건 단위로 Fetch (Java)
3. 부분범위처리 원리 활용
- SQL*Plus에서
set arraysize 100
으로 할 경우 301번의 Fetch Call로 30,000 rows를 읽는 것을 Trace에서 확인이 가능하다. - 만약 10개의 record를 담는 block이 3개 있는 경우 ArraySize를 3으로 할 경우 총 10번의 Fetch Call이 발생하지만, Block I/O는 12번이 된다. (왜냐면 4번째 Call의 경우 Block 1에서 1 record만 읽고, Block 2에서 2 record만 읽는다. 즉 2번의 Block I/O가 발생한 것이다.)
- 즉, ArraySize를 무작정 키운다고 좋은게 아니다.
4. 효과적인 화면 페이지 처리
- Web에서 Page 단위로 게시물을 보여줄 경우 처음부터 다 읽어서 Fetch하면서 필요한 것만 보여주게 된다면 성능에 치명적이다.
- 필요한 Page부분만 읽도록 수정해야 한다. (뒤에서 자세히 다루겠다.)
5. 분산 Query
원격에 있는 DB 간의 Table을 JOIN할 경우 성능을 높일 수 있는 방안은 ?
- 실행계획을 보고 다음의
Rows
수를 확인한다.- 실제 결과 데이터
- 각 Table 별로 필터 조건으로 걸러지는 데이터
REMOTE
로 원격으로 전송된 데이터
/*+ driving_site(테이블명) */
Hint를 활용하여 어느 Server에서 JOIN을 수행하고 나머지 Server에서 원격으로 데이터를 전송하도록 조절한다.- 결론적으로 네트워크를 통해 데이터 전송량을 줄이는게 핵심이다.
6. User-defined Function/Procedure
- Built-In Function/Procedure : Native code로 완전 compile된 형태이므로 엄청 빠르다.
User-defined Function/Procedure : 실행할때마다 Virtual Machine에서 별도 실행 엔진을 통해 호출되므로 느리다.
- 특히나 내부적으로 또다른 Query가 있을 경우에는 Execute Call, Fetch Call이 재귀적으로 일어난다.
- 그러므로 다음의 경우에만 제한으로 사용할 것을 권장한다.
- 소량의 데이터를 조회할 경우
- 부분범위처리가 가능한 상황
- 가급적으로 One SQL로 구현하고자 노력하는게 좋다. (JOIN, Scalar-Subquery)
- 그 과정에서 별도의 Logic이 필요한 것을 따로 Table로 구현하는 방법도 있다.
- ex. 휴무일을 검사하는 Function 대신 업무일 Table을 생성해두고
EXISTS
로 비교
- ex. 휴무일을 검사하는 Function 대신 업무일 Table을 생성해두고
- 그 과정에서 별도의 Logic이 필요한 것을 따로 Table로 구현하는 방법도 있다.
1.4 DB I/O 원리
Block I/O
- 모든 DBMS는 Block단위 I/O를 한다
- 1개의 record를 읽더라도 해당 Block 전체를 읽는다.
- Buffer Cache, Disk상의 Datafile 모두 Block 단위 I/O로 읽는다.
- Datafile -> DB Buffer Cache로 Block을 적재할 때
- Datafile에서 Block을 직접 읽고 쓸때
- DB Buffer Cache에서 Block을 읽고 쓸때
- DB Buffer Cache -> Datafile로 다시 데이터를 쓸때
Buffer Cache Hit Ratio
BCHR = (버퍼 캐시에서 바로 찾은 Block수 / 총 읽은 Block수) X 100
Fetch Call 에서 disk : 18 , query : 822 일 경우 총 읽은 Block수는 822, 버퍼 캐시에서 읽은 수는 822 - 18 = 804 이므로 BCHR = 97.8%가 된다.
Sequential I/O vs. Random I/O
- Sequential I/O : Index에서 Leaf Node를 따라 Scan하는 것과 Full Table Scan
- Random I/O : Index에서 한 건을 읽기 위해 한 Block씩 접근하는 방식
- Sequential 액세스의 비중을 높이고 Random 액세스의 발생량을 줄이는게 I/O 튜닝의 핵심이다.
- Sequential 액세스의 효율을 높일려면 총 읽은 건수 중 결과집합으로 선택되는 비중을 높여야 한다. 즉 같은 결과를 얻기 위해 얼마나 적은 record를 읽느냐로 판단한다.
- Index의 컬럼 및 그 순서를 조정하는게 가장 좋은 방법이다.
Single Block I/O vs. MultiBlock I/O
- Index : Single Block I/O (10g이후 Index Range Scan, Index Full Scan에서는 MultiBlock I/O로도 가능)
- Table : MultiBlock I/O
- 대량의 데이터를 읽을 땐 MultiBlock I/O가 I/O Call을 줄일 수 있기 때문에 유리하다.
I/O 효율화 원리
- 필요한 최소 Block만 읽도록 SQL 작성
- 최적의 Optimizer 팩터 제공
- 전략적 Index 구성
- DBMS가 제공하는 기능 활용 : Index, Partition, Cluster, Window 함수
- 필요하다면 Optimizer Hint를 활용하여 최적의 액세스 경로로 유도
- 전체처리 최적화, 최초 응답속도 최적화 및 그밖에 파라메터들
- 통계정보 제공
이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)