(1) 공식기반 옵티마이저
- 오라클 데이터베이스에서 기본적으로 제공하는 15단계의 실행방법 중 어떤 방법을 선택하여 실행하느냐에 따라 성능이 결정되는 방법입니다.
개발자 자신이 실행하는 SQL문의 문장에 의해 성능이 좌우되는 방법입니다.
- 우선 순위
1) ROWID에 의한 단일행 실행
2) Cluster-Join에 의한 단일행 실행
3) Unique-Key, Primary-Key를 사용한 Hash-Cluster Key에 의한 단일행 실행
4) Unique-Key, Primary-Key에 의해 생성된 인덱스를 통한 동등 검색
5) Cluster 조인
6) Hash-Cluster Key
7) 인덱스화된 Cluster-Key
8) 복합 인덱스
9) 단일 컬럼 인덱스에 의한 동등 검색
10) 인덱스가 구축된 컬럼에 대한 제한된 범위 검색(BETWEEN, LIKE, < AND >, = 표현식)
11) 인덱스가 구축된 컬럼에 대한 무제한 범위의 검색(>=, =< 표현식)
12) 정렬-병합 조인
13) 인덱스가 구축된 컬럼에 대한 MAX, MIN
14) 인덱스가 구축된 컬럼에 대한 ORDER BY
15) Full-Table Scan


09 30, 2010 08:50 09 30, 2010 08:50

Trackback URL : http://develop.sunshiny.co.kr/trackback/552

Leave a comment

Oracle - 옵티마이저의 한계

Posted 09 26, 2010 23:29, Filed under: Study/OracleClub

03. 옵티마이저의 한계

앞서 옵티마이저 행동에 영향을 미치는 요소들에 대해 살펴보았는데, 강조하고자 한 핵심은 옵티마이저가 절대 완벽할 수 없다는 것이다.
옵티마이저도 결국 사람이 만든 소프트웨어 엔진에 불과하며, 모든 프로그램이 업그레이드를 통해 조금씩 개선되듯 옵티마이저도 여러 가지 제약과 한계점들을 극복하며 발전해 나가는 과정 속에 있다.
기술적으로 도저히 극복할 수 없는 한계점들이 있는가 하면, 정보를 저장할 공간과 시간 제약(특히 OLTP 환경에서 파싱에 허용되는 시간은 극히 짧음)때문에 아직 적용하지 못하는 최적화 기법들도 있다.
`자동 튜닝 옵티마이저`라 불리는 오프라인 옵티마이저가 이런 사실을 잘 말해 주는데,
`튜닝 모드`에서 작동하는 이 옵티마이저는 시간에 구애받지 않고 충분한 시간 동안 동적 샘플링을 포함한 여러 기법을 활용해 튜닝을 실시하고 사용자에게 권고안을 제시한다.

자동 튜닝 옵티마이저

자동 튜닝 옵티마이저(Automatic Tuning Optimizeer)를 `오프라인 옵티마이저`라고도 한다.
반대로 말해, 우리가 흔히 말하는 옵티마이저는 `온라인 옵티마이저` 또는 `런타임 옵티마이저`인 셈이다.
자동 튜닝 옵티마이저는 통계를 분석하고, SQL 프로파일링을 실시하며, 액세스 경로 및 SQL 구조 분석을 통해 SQL 튜닝을 실시한다.
튜닝 모드에서 작동하는 이 옵티마이저에게는 한 문장을 튜닝하는 데에 런타임 옵티마이저보다 훨씬 긴 시간이 주어진다.
넉넉한 시간 동안 풍부한 정보를 수집, 활용함으로써 데이터 액세스 비용과 카디널리티를 보다 정확하게 계산할 수 있다.
예를 들어, 동적 샘플링을 통해 부가적인 정보를 수집하고, 심지어 부분적인 실행을 통해 예측치(조인 카디널리티 등)를 검증함으로써 잘못된 정보를 조정하는 테크닉을 사용한다.
그렇게 각 SQL 단위로 수집된 프로파일(상관관계 있는 컬럼 간 결합 분포, 조인에 의한 테이블 간 상관관계 등 해당 SQL만을 위한 보조적인 통계정보)을 데이터 딕셔너리에 영구 저장해 런타임 옵티마이저가 참조할 수 있도록 하는 기능도 제공한다.(sqltune_category 파라미터 참조)
자동 튜닝 옵티마이저 기능을 활용하려면 `SQL Tuning Advisor`라 불리는 서버 유틸리티를 이용하면 된다.
SQL Tuning Advisor에 SQL문을 입력하면 내부적으로 자동 튜닝 옵티마이저를 호출해 SQL 분석을 실시한다.
분석이 완료되면 SQL 성능을 높이기 위해 사용자가 취해야 할 조치사항들을 보고서 형태로 출력해 준다.

자동 튜닝 옵티마이저(SQL Tuning Advisor) 활용
SYS@ora10g>grant advisor to scott ;
Grant succeeded.
SYS@ora10g>conn scott/tiger
Connected.
SCOTT@ora10g>CREATE TABLE test_advisor (id CONSTRAINT t_pk PRIMARY KEY, pad) AS
 SELECT rownum, lpad('*',4000,'*')
  FROM all_objects
  WHERE rownum <= 10000 ;

Table created.
SCOTT@ora10g>VARIABLE tn VARCHAR2(30)
SCOTT@ora10g>DECLARE
  l_sqltext CLOB := 'SELECT COUNT(*) FROM test_advisor WHERE id+42 = 126' ;
  BEGIN
  :tn := dbms_sqltune.create_tuning_task(sql_text=>l_sqltext) ;
  dbms_sqltune.execute_tuning_task(:tn) ;
  END;
  /

PL/SQL procedure successfully completed.

SCOTT@ora10g>print TN
TN
--------------------------------
TASK_1366

SCOTT@ora10g>set long 20000
SCOTT@ora10g>SELECT dbms_sqltune.report_tuning_task(:tn) FROM dual ;

DBMS_SQLTUNE.REPORT_TUNING_TASK(:TN)
--------------------------------------------------------------------------------
GENERAL INFORMATION SECTION
-------------------------------------------------------------------------------
Tuning Task Name                  : TASK_1366
Tuning Task Owner                 : SCOTT
Scope                             : COMPREHENSIVE
Time Limit(seconds)               : 1800
Completion Status                 : COMPLETED
Started at                        : 09/25/2010 21:10:54
Completed at                      : 09/25/2010 21:10:55
Number of Statistic Findings      : 1
Number of Index Findings          : 1
Number of SQL Restructure Findings: 1

-------------------------------------------------------------------------------
Schema Name: SCOTT
SQL ID     : 5pha323u5skht
SQL Text   : SELECT COUNT(*) FROM test_advisor WHERE id+42 = 126

-------------------------------------------------------------------------------
FINDINGS SECTION (3 findings)
-------------------------------------------------------------------------------

1- Statistics Finding
---------------------
  Table "SCOTT"."TEST_ADVISOR" was not analyzed.

  Recommendation
  --------------
  - Consider collecting optimizer statistics for this table.
    execute dbms_stats.gather_table_stats(ownname => 'SCOTT', tabname =>
            'TEST_ADVISOR', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
            method_opt => 'FOR ALL COLUMNS SIZE AUTO');

  Rationale
  ---------
    The optimizer requires up-to-date statistics for the table in order to
    select a good execution plan.

2- Index Finding (see explain plans section below)
--------------------------------------------------
  The execution plan of this statement can be improved by creating one or more
  indices.

  Recommendation (estimated benefit: 100%)
  ----------------------------------------
  - Consider running the Access Advisor to improve the physical schema design
    or creating the recommended index.


DBMS_SQLTUNE.REPORT_TUNING_TASK(:TN)
--------------------------------------------------------------------------------
    create index SCOTT.IDX$$_05560001 on SCOTT.TEST_ADVISOR('ID'+42);

  Rationale
  ---------
    Creating the recommended indices significantly improves the execution plan
    of this statement. However, it might be preferable to run "Access Advisor"
    using a representative SQL workload as opposed to a single statement. This
    will allow to get comprehensive index recommendations which takes into
    account index maintenance overhead and additional space consumption.

3- Restructure SQL finding (see plan 1 in explain plans section)
----------------------------------------------------------------
  The predicate "TEST_ADVISOR"."ID"+42=126 used at line ID 2 of the execution
  plan contains an expression on indexed column "ID". This expression prevents
  the optimizer from selecting indices on table "SCOTT"."TEST_ADVISOR".

  Recommendation
  --------------
  - Rewrite the predicate into an equivalent form to take advantage of
    indices. Alternatively, create a function-based index on the expression.

  Rationale
  ---------
    The optimizer is unable to use an index if the predicate is an inequality
    condition or if there is an expression or an implicit data type conversion
    on the indexed column.

-------------------------------------------------------------------------------
EXPLAIN PLANS SECTION
-------------------------------------------------------------------------------

1- Original
-----------
Plan hash value: 454320086

------------------------------------------------------------------------------
| Id  | Operation             | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |     1 |    13 |     7   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE       |      |     1 |    13 |            |          |
|*  2 |   INDEX FAST FULL SCAN| T_PK |    85 |  1105 |     7   (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("ID"+42=126)


DBMS_SQLTUNE.REPORT_TUNING_TASK(:TN)
--------------------------------------------------------------------------------

2- Using New Indices
--------------------
Plan hash value: 2428417073

------------------------------------------------------------------------------------
| Id  | Operation         | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                |     1 |    13 |     4   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |                |     1 |    13 |            |
|
|*  2 |   INDEX RANGE SCAN| IDX$$_05560001 |    10 |   130 |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("TEST_ADVISOR".???)

-------------------------------------------------------------------------------

(1) 부족한 옵티마이징 팩터

옵티마이저는 주어진 환경에서 최선을 다할 뿐 적절한 옵티마이징 팩터를 제공하는 것은 결국 사람의 몫이다.

(2) 부정확한 통계

현실적인 제약 때문에 샘플링 방식으로 통계를 수집하다 보니 실제 데이터와 불일치가 발생하기 마련이다.

(3) 히스토그램의 한계

부정확한 통계의 연장선으로 볼 수 있는데, 히스토그램 버킷 개수로 254개까지만 허용된다는 점도 옵티마이저에겐 중요한 제약사항이다.

(4) 바인드 변수 사용시 균등분포 가정

조건절에 바인드 변수를 사용하면 옵티마이저가 균등분포를 가정하고 비용을 계산한다.

(5) 결합 선택도 산정의 어려움

조건절 컬럼이 서로 상관관계에 있으면 정확한 데이터 분포와 카디널리티를 산정하기 어렵다.

select * from 사원 where 직급 = '부장' and 연봉 >= 5000;

(6) 비현실적인 가정

I/O 비용 모델 하에서의 비용은 단순히 I/O Call 횟수를 의미한다. 그런데 I/O 비용 모델이 사용하는 기본 가정에 따르면 Single Block Read와 Multiblock Read의 비용은 같다.
옵티마이저는 이 둘을 구분하지 않고 동일한 하나의 시스템 Call로 간주하는 것이다.
또한 옵티마이저는 다른 세션이나 다른 쿼리문에 의해 데이터 블록들이 이미 버퍼 캐시에 캐싱돼 있을 가능성을 배제한다.

(7) 규칙에 의존하는 CBO

아무리 비용기반 옵티마이저라 하더라도 부분적으로는 규칙에 의존한다.
예를 들어, 원격 테이블이나 External 테이블에 대해서는 카디널리티, 평균 행 길이, 블록 수, 그리고 각종 인덱스 관련 통계항목들에 대해 고정된 상수 값을 사용
옵티마이저 모드를 first_rows로 설정했을 때 order by 소트를 대체할 인덱스가 있으면 부조건 인덱스를 사용
9i까지 옵티마이저는 뷰 또는 서브쿼리를 만나면 무조건 쿼리 블록을 풀어 메인 쿼리와 Merging하려고 시도

알파벳순 인덱스 선택 규칙

CBO가 사용하는 규칙과 관련해 꼭 기억해야할 사항이 있는데, 두 대안 인덱스의 예상 비용이 같을 때 알파벳 순에서 앞선 것을 선택한다는 사실이다.(인덱스 명명 규칙의 중요성 시사)

SCOTT@ora10g>create table t_3
  as
  2    3    select rownum a, rownum b from dual
  4    connect by level <= 10000 ;

Table created.

SCOTT@ora10g>create index t_x01 on t_3(a) ;
Index created.

SCOTT@ora10g>create index t_x02 on t_3(b) ;
Index created.

SCOTT@ora10g>exec dbms_stats.gather_table_stats(user, 't_3') ;
PL/SQL procedure successfully completed.

-- a, b 두 컬럼을 가진 테이블을 생성하고 100% 같은 값을 입력했다.
-- 그리고 각 컬럼에 단일 컬럼 인덱스를 생성해 두었다.

SCOTT@ora10g>set autotrace traceonly exp
SCOTT@ora10g>select * from t_3 where a = 1 and b = 1 ;

--------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |     7 |     2   (0)|
|*  1 |  TABLE ACCESS BY INDEX ROWID| T_3   |     1 |     7 |     2   (0)|
|*  2 |   INDEX RANGE SCAN          | T_X01 |     1 |       |     1   (0)|
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("B"=1)
   2 - access("A"=1)

위와 같은 조건절을 만났을 때 t_x01, t_x02 둘 중 어느 것을 선택하든 쿼리 수행 비용은 같다.
이때 옵티마이저가 t_x01 인덱스를 선택한 판단 근거는, 허무하게도 인덱스명의 알파벳 순이다.
t_x01 인덱스명을 t_x03으로 바꾸고 나서 실행계획을 다시 확인하면, t_x02 인덱스가 사용되는 것을 볼수 있다.

SCOTT@ora10g>alter index t_x01 rename to t_x03 ;
Index altered.

SCOTT@ora10g>select * from t_3 where a = 1 and b = 1 ;

--------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |     1 |     7 |     2   (0)|
|*  1 |  TABLE ACCESS BY INDEX ROWID| T_3   |     1 |     7 |     2   (0)|
|*  2 |   INDEX RANGE SCAN          | T_X02 |     1 |       |     1   (0)|
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("A"=1)
   2 - access("B"=1)

CBO가 이런 단순한 규칙을 사용함으로 인해 가끔 실행계획에 문제가 생기는 것을 보게 된다.
예를 들어, 테이블명_PK, 테이블명_N01, 테이블명_N02와 같은 식으로 인덱스 명명 규칙을 정했다고 하자.

ORDER_PK  : 고객번호 + 주문일자
ORDER_N01 : 고객번호 + 배송일자

인덱스가 위와 같은 상황에서 대부분 고객이 평균적으로 두서너 달에 한 번씩 주문한다면,
아래 쿼리에 대한 PK, N01 인덱스의 예상 비용이 똑같을 수 있다. 두 인덱스 모두 카디널리티가 1미만이기 때문이다.

where 고객번호 = :cost_no
and   주문일자 = :ord_dt

비용이 어떻든 간에 PK 구성 컬럼이 모두 등치(=) 조건으로 조건절에 사용되고 인덱스 높이도 같다면, 상식적으로 PK 인덱스를 사용하는 것이 최선의 선택이다.
그런데도 옵티마이저는 알파벳 순에 따라 N01 인덱스를 선택한다.
이때, 거의 매일 주문을 일으키는 우수 고객이 입려되면 N01 인덱스는 주문일자를 필터링하려고 엄청나게 많은 테이블 Random 액세스를 일으킬 것이다.
이처럼 두 인덱스의 예상 비용이 같아 비합리적인 인덱스 선택이 이루어지는 것은 매우 흔한 일이다.
그런 현상을 발견했을 때 인덱스명을 바꿔줄 수 있으면 좋겠지만 실제 운영 중인 시스템에서 그러기는 쉽지 않다.
적어도 Unique한 조건으로 PK 인덱스를 액세스해야 하는 상황에서 이런 일이 발생하지 않게 하려면 인덱스 명명 규칙을 PK, X01, X02와 같은 식으로 정하는 것이 좋다. 참고로, 예상 비용이 같으면 오브젝트ID가 큰 것을 우선적으로 선택하던 시절도 있었다.

(8) 하드웨어 성능 특성

옵티마이저는 기본적으로 옵티마이저 개발팀이 사용한 하드웨어 사양에 맞춰져 있다.
따라서 실제 운영 시스템의 하드웨어 사양이 그것과 다를 때 옵티마이저가 잘못된 실행계획을 수립할 가능성이 높아진다.
또한 애플리케이션 특성(I/O 패턴, 부하 정도 등)에 의해서도 하드웨어 성능은 달라진다.

동적 실시간 최적화(Dynamic Runtime Optimizations)

모든 데이터베이스의 작업 부하는 매우 가변적이다.
따라서 하드웨어 성능 특성을 반영한 실행계획을 수립하더라도 쿼리 수행 당시 시스템 부하 정도에 따라 최적이 아닐 수 있다.
정적인 통계정보와 옵티마이저 모델로는 이런 한계점을 극복하기 어려우므로 시스템 부하에 따라 실행전략을 동적으로 조정하는 최적화 기법이 도입되고 있다.
이 기능의 핵심은 쿼리가 수행되는 시점의 시스템 상태에 따라 하드웨어 리소스(CPU와 메모리)를 적절히 배분해 주는 데 있다.
대표적으로, 시스템 부하 정도에 따라 병렬 쿼리의 프로세스 개수를 오라클이 동적으로 조절해 주는 기능을 들 수 있다.
또한 9i부터 PGA 메모리 크기를 자동으로 조절해 주기 시작했고, 10g부터는 SGA를 구성하는 서브 메모리 영역을 자동으로 조절해 주는 기능도 소개되었다.
이런 기능을 통해 고정된 하드웨어 리소스와 정해진 시간 동안, 개별 SQL이 아닌 전체 SQL의 처리량을 극대화할 수 있다.
쿼리 최적화가 단일 SQL문 성능을 최적화하는 데 초점을 맞추는 반면, 동적 실시간 최적화는 수많은 SQL이 동시에 수행되는 환경에서 스스템 전체 최적화를 이루는 데 초점을 맞춘다.

문서에 대하여


# 이 문서는 오라클클럽에서 작성하였습니다.
# 이 문서를 다른 블로그나 홈페이지에 게재하실 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^
# 출처 : http://www.gurubee.net/pages/viewpage.action?pageId=6259450&
09 26, 2010 23:29 09 26, 2010 23:29

Trackback URL : http://develop.sunshiny.co.kr/trackback/548

Leave a comment

Oracle - 옵티마이저(Optimizer)

Posted 09 26, 2010 23:23, Filed under: Study/OracleClub

01. 옵티마이저

(1) 옵티마이저란?

옵티마이저(Optimizer)는 사용자가 요청한 SQL을 가장  효율적이고 빠르게 수행할 수 있는 최적(최저비용)의 처리경로를 선택해주는 DBMS의 핵심엔진이다.
구조화된 질의언어(SQL)로 사용자가 원하는 결과집합을 정의하면 이를 얻는 데 필요한 처리절차(프로시저)는 DBMS에 내장된 옵티마이저가 자동으로 생성해준다.

사용자 삽입 이미지
 
옵티마이저에는 크게 두 가지가 있다.

  • 규칙기반 옵티마이저(Rule-Based Optimizer, 이하 RBO)
  • 비용기반 옵티마이저(Cost-Based Optimizer, 이하 CBO)

(2) 규칙기반 옵티마이저

규칙기반 옵티마이저(RBO)는 다른 말로 `휴리스틱(Heuristic)옵티마이저`라고 불리며, 미리 정해놓은 우선순위에 따라 액세스 경로(Access Path)를 평가하고 실행계획을 선택한다.
아래 표는 RBO가 사용하는 규칙(액세스 경로별 우선순위)인데, 인덱스 구조, 연산자, 조건절 형태가순위를 결정짓는 주요인임을 알 수 있다.

사용자 삽입 이미지





















 
OLTP 환경의 중소형 데이터베이스 시스템이라면 RBO 규칙이 어느 정도 보편 타당성을 갖는다.
하지만 데이터량, 값의 수(number of distinct value), 컬럼 값 분포, 인덱스 높이, 클러스터링 팩터같은 데이터 특성을 고려하지 않기 때문에 RBO는 대용량 데이터를 처리하는 데 있어 합리적이지 못할때가 많다.
예를 들어, 조건절 컬럼에 인덱스가 있으면 무조건 인덱스를 사용한다.
항상 인덱스를 신뢰하며, Full Table Scan과의 손익을 따지지 않는다.(인덱스를 경유하면서 액세스할 데이터량이 일정 수준 이상이면 오히려 Full Table Scan이 유리하다는 것은 일반적인 상식이지만 RBO는 그런 사실을 외면한다.)
또 다른 예로, 아래와 같은 문장을 수행할 때도 empno 컬럼에 인덱스가 있으면 무조건 그 인덱스를 이용해 sort order by 연산을 대체한다.
부분범위처리가 불가능한 상황이라면 Full Table Scan 하고 나서 정렬하는 편이 낳은데도 RBO 우선순위로는 인덱스 컬럼에 의한 order by(14위)가 Full Table Scan(15위)보다 한 단계 높아서 그런 선택을 하는 것이다.

SYS@ora10g>select /*+ rule */ * from scott.emp order by empno ;

--------------------------------------------------
| Id  | Operation                   | Name       |
--------------------------------------------------
|   0 | SELECT STATEMENT            |            |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP        |
|   2 |   INDEX FULL SCAN           | PK_EMP_IDX |
--------------------------------------------------
Note
-----
   - rule based optimizer used (consider using cbo)

RBO는 이처럼 예측 가능하고 일관성 있는 실행계획을 수립하며 사용자가 원하는 처리경로로 유도하기가 쉽다.
그에 반해 CBO는 같은 SQL이더라도 데이터 특성에 따라 실행계획이 달라지고 복잡한 비용 원리를 내포하고 있어 이를 정확히 이해하지 못한다면 제어가 쉽지 않다.

(3) 비용기반 옵티마이저

비용기반 옵티마이저는 말 그대로 비용을 기반으로 최적화를 수행한다.
여기서 `비용(Cost)`이란, 쿼리를 수행하는데 소요되는 일량 또는 시간을 뜻한다.
전통적인 I/O 비용 모델에서는 I/O 요청(Call) 횟수만을 비용으로 평가했지만, 최근 도입된 CPU 비용 모델에서는 CPU 연산 비용까지 감안한다.
그리고 수행 일량을 상대적인 시간 개념으로 환산해 비용을 평가한다.
CBO가 실행계획을 수립할 때 판단 기준이 되는 비용은 어디까지나 예상치다.
미리 구해놓은 테이블과 인덱스에 대한 여러 통계정보를 기초로 각 오퍼레이션 단계별 예상 비용을 산정하고, 이를 합산한 총 비용이 가장 낮은 실행계획 하나를 선택한다.
비용을 산정할 때 사용되는 오브젝트 통계 항목에는 레코드 개수, 블록 개수, 평균 행 길이, 컬럼 값의 수, 컬럼 값 분포, 인덱스 높이, 클러스터링 팩터 같은 것들이 잇다.
옵티마이저의 최적화 수행단계를 요약하면 다음과 같다.
1. 사용자가 던진 쿼리수행을 위해, 후보군이ㅣ 될만한 실행계획을 찾는다.
2. 데이터 딕셔너리(Data Dictionary)에 미리 수집해 놓은 오브젝트 통계 및 시스템 통계정보를 이용해 각 실행계획의 예상비용을 산정한다.
3. 각 실행계획의 비용을 비교해서 최저비용을 갖는 하나를 선택한다.

사용자 삽입 이미지



















 
 

동적 샘플링(Dynamic Sampling)

쿼리를 최적화할 때 미리 구해놓은 통계정보를 이용한다고 했는데, 만약 테이블과 인덱스에 대한 통계정보가 없거나 너무 오래되어 신뢰할수없을 때 옵티마이저가 동적으로 샘플링을 수행하도록 할 수 있다.
optimizer_dynamic_sampling 파라미터로 동적 샘플링 레벨을 조정하며, 9i에서 기본 레벨이 1이던 것이 10g에서 2로 상향 조정되었다.
따라서 10g에서는 쿼리 최적화 시 통계정보 없는 테이블을 발견하면 무조건 동적 샘플링을 수행한다.
레벨을 0으로 설정해 동적 샘플링이 일어나지 않게 할 수 있으며, 9i 기본 값인 1로 설정할 때는 아래 조건을 모두 만족할 때만 동적 샘플링이 일어난다.
(1) 통계정보가 수집되지 않은 테이블이 적어도 하나 이상 있고,
(2) 그 테이블이 다른 테이블과 조인되거나 서브쿼리 또는 Non-mergeable View에 포함되고,
(3) 그 테이블에 인덱스가 하나도 없고,
(4) 그 테이블에 할당된 블록 수가 32개(동적 샘플링을 위한 표본 블록 수의 기본 값)보다 많을때
 
레벨 설정은 최대 10까지 가능하다. 레벨이 높을수록 옵티마이저는 더 적극적인 동적 샘플링을 수행하며 샘플링에 사용되는 표본 블록 개수도 증가한다.

아래 그림은 CBO를 기준으로 SQL 처리 절차를 요약한 것이다.

사용자 삽입 이미지

 

  • 1. Query Transformer : 사용자가 던진 SQL을 그대로 최적화하는 것이 아니라 우선 최적화하기 쉬운 형태로 변환을 시도한다.
    물론 쿼리 변환 전후 결과가 동일함이 보장될 때만 그렇게 한다.
  • 2. Estimator : 쿼리 오퍼레이션 각 단계의 선택도(Selectivity), 카디널리티(Cardinality), 비용(Cost)을 계산하고, 궁극적으로는 실행계획 전체에 대한 총 비용을 계산한다.
  • 3. Plan Generator : 하나의 쿼리를 수행하는 데 있어, 후보군이 될만한 실행계획들을 생성해내는 역할을 한다.
스스로 학습하는 옵티마이저(Self-Learning Optimizer)

v$sql, v$sql_plan_statistics, v$sql_plan_statistics_all, v$sql_workarea 등에 SQL별로 저장된 수많은 런타임 수행 통계를 보면 앞으로 옵티마이저의 발전 방향을 예상할 수 있다.
옵티마이저는 지금까지 오브젝트 통계와 시스템 통계로부터 산정한 `예상` 비용만으로 실행계획을 수립했지만 앞으로는 예상치가 빗나갔을 때 이들 런타임 수행 통계를 보고 실행계획을 조정할 움직임을 보이고 있다.

(4) 옵티마이저 모드

옵티마이저 모드로 선택할 수 있는 값으로는 아래 5가지가 있고, 시스템 레벨, 세션 레벨, 쿼리 레벨에서 바꿀 수 있다.

  • rule
  • all_rows
  • first_rows
  • first_rows_n
  • choose
    alter system set optimizer_mode = all_rows ;  -- 시스템 레벨 변경
    alter session set optimizer_mode = all_rows ; -- 세션 레벨 변경
    select /*+ all_rows */ * from t where ... ;   -- 쿼리 레벨 변경
RULE

RBO 모드를 선택하고자 할 때 사용한다.

ALL_ROWS

쿼리 최종 결과집합을 끝까지 Fetch하는 것을 전제로, 시스템 리소스(I/O, CPU, 메모리 등)를 가장 적게 사용하는 실행계획을 선택한다.

FIRST_ROWS

전체 결과집합 중 일부 로우만 Fetch하다가 멈추는 것을 전제로, 가장 빠른 응답 속도를 낼 수 있는 실행계획을 선택한다.
사용자가 만약 끝까지 Fetch한다면 오히려 더 많은 리소스를 사용하고 전체 수행 속도도 느려질 수 있다.
first_rows는 비용과 규칙(=휴리스틱)을 혼합한 형태의 옵티마이저 모드다.
얼마만큼을 Fetch할지 지정하지 않았으므로 정확한 비용을 예측할 수 없고, 따라서 옵티마이저는 내부적으로 정해진 규칙을 사용한다.

실제 사례를 보면서 first_rows 모드일 때의 CBO 작동원리를 이해해 보자.

SYS@ora10g>create table t_emp
  2  as 
  3  select * from scott.emp, (select rownum no from dual connect by level <= 1000)
  4  order by dbms_random.value ;

Table created.

SYS@ora10g>alter table t_emp add constraint t_emp_pk primary key(empno, no) ;

Table altered.

SYS@ora10g>begin
  2  dbms_stats.gather_table_stats(
  3  ownname => user
  4  , tabname => 't_emp'
  5  , method_opt => 'for columns sal') ;
  6  end;  
  7  /

PL/SQL procedure successfully completed.

SYS@ora10g>set autotrace traceonly exp
SYS@ora10g>select /*+ all_rows */ * from t_emp
  2  where sal >= 5000
  3  order by empno, no ;

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |   999 | 40959 |    25   (8)| 00:00:01 |
|   1 |  SORT ORDER BY     |       |   999 | 40959 |    25   (8)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| T_EMP |   999 | 40959 |    24   (5)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("SAL">=5000)

위에서는 all_rows 모드이므로 Table Full Scan하고 나서 소트 연산을 수행하는 실행계획이 수립되었다.
first_rows로 바꾸고 다시 수행해 보자.

SYS@ora10g>select /*+ first_rows */ * from t_emp
  2    where sal >= 5000
  3    order by empno, no ;

----------------------------------------------------------------------------------------
| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |          |   999 | 40959 | 14903   (1)| 00:02:59 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T_EMP    |   999 | 40959 | 14903   (1)| 00:02:59 |
|   2 |   INDEX FULL SCAN           | T_EMP_PK | 15000 |       |    39   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("SAL">=5000)

같은 SQL이지만 first_rows 모드로 바꾸자 order by 컬럼 순으로 정렬된 PK 인덱스를 사용하는 실행계획이 수립되었다.
이 실행계획을 수립하는 데에는 비용보다 규칙이 우선시되었다.

아래 쿼리 결과에서 보듯 최종 결과집합에 해당하는 레코드 비율은 7%밖에 되지 않는다.
따라서 첫 번째 Fetch 분량을 얻기까지 많은 인덱스 스캔과 테이블 액세스를 수반하므로 first_rows가 지향하는 최초 응답속도도 생각만큼 좋지 않을 수 있다.

SYS@ora10g>set autotrace off
SYS@ora10g>select count(*) all_emp
  2    , count(case when sal >= 5000 then 1 end) over_5000
  3    , round(count(case when sal >= 5000 then 1 end) / count(*) * 100) ratio
  4    from t_emp ;


   ALL_EMP  OVER_5000      RATIO
---------- ---------- ----------
     15000       1000          7

규칙의 영향을 받긴 하지만 first_rows도 CBO 모드이기 때문에 통계정보를 활용한다.
예를 들어, 위에서 컬럼 히스토그램을 생성했으므로 [sal >= 5001]인 사원이 없다는 사실만큼은 옵티마이저도 알 수 있다.
따라서 이 조건절을 사용하면 실행계획도 아래와 같이 바뀐다.

SYS@ora10g>set autotrace traceonly exp
SYS@ora10g>select /*+ first_rows */ * from t_emp
  2  where sal >= 5001
  3  order by empno, no ;

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |    41 |    25   (8)| 00:00:01 |
|   1 |  SORT ORDER BY     |       |     1 |    41 |    25   (8)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| T_EMP |     1 |    41 |    24   (5)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("SAL">=5001)

RBO와의 차이점이 바로 여기에 있다.
같은 조건을 주고 RBO 모드로 실행해 보면 아래와 같이 여전히 Index Full Scan을 선택한다.

consider using cbo

아래 실행계획의 Note에서 CBO 사용을 고려하라는 안내를 해줌.

SYS@ora10g>select /*+ rule */ * from t_emp
  2  where sal >= 5001
  3  order by empno, no ;

------------------------------------------------
| Id  | Operation                   | Name     |
------------------------------------------------
|   0 | SELECT STATEMENT            |          |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T_EMP    |
|   2 |   INDEX FULL SCAN           | T_EMP_PK |
------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("SAL">=5001)

Note
-----
   - rule based optimizer used (consider using cbo)
FIRST_ROWS_N

사용자가 처음 n개 로우만 Fetch하는 것을 전제로, 가장 빠른 응답 속도를 낼 수 있는 실행계획을 선택한다.
n으로 지정할 수 있는 값은 1, 10, 100, 1000 네 가지며, 사용자가 지정한 n개 로우 이상을 Fetch한다면 오히려 더 많은 리소스를 사용하고 전체 수행 속도도 느려질 수 있다.

alter session set optimizer_mode = first_rows_100 ;
select /*+ first_rows(100) */ * from t where ... ;

힌트를 사용할 때는 괄호 안에 0보다 큰 어떤 정수 값이라도 입력 가능하므로 파라미터를 이용할 때보다 더 정밀하게 제어할 수 있다.
first_rows와 달리 first_rows_n응 완전한 CBO 모드로 작동한다.
예를 들어, first_rows_100이면 100개 로우를 가장 빨리 리턴할 수 있는 최저비용의 실행계획을 선택하며, Table Full Scan비용이 오히려 낮다면 그것을 선택한다.

SYS@ora10g>select count(*) all_emp
  2  , count(case when sal >= 2000 then 1 end) over_2000
  3  , round(count(case when sal >= 2000 then 1 end) / count(*) * 100) ratio
  4  from t_emp ;


   ALL_EMP  OVER_2000      RATIO
---------- ---------- ----------
     15000       6000         40

위 쿼리 결과에서 보듯 sal >= 2000인 사원은 6,000명으로서 전체 중 43%를 차지하므로 일정량 이상을 Fetch하는 순간 오히려 Table Full Scan 보다 비용이 커진다.

SYS@ora10g>select /*+ first_rows(10) */ * from t_emp
  where sal >= 2000
  2    3    order by empno, no ;

----------------------------------------------------------------------------------------
| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |          |    10 |   410 |    27   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T_EMP    |  6000 |   240K|    27   (0)| 00:00:01 |
|   2 |   INDEX FULL SCAN           | T_EMP_PK |    26 |       |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("SAL">=2000)

SYS@ora10g>select /*+ first_rows(100) */ * from t_emp
  where sal >= 2000
  2    3    order by empno, no ;

------------------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |  6000 |   240K|       |    91   (3)| 00:00:02 |
|   1 |  SORT ORDER BY     |       |  6000 |   240K|   776K|    91   (3)| 00:00:02 |
|*  2 |   TABLE ACCESS FULL| T_EMP |  6000 |   240K|       |    24   (5)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("SAL">=2000)

first_rows(10) 일 때는 Index Full Scan 하다가 first_rows(100) 일 때 Table Full Scan으로 바쒸었다.
비용을 고려해 실행계획을 선택했음을 알 수 있다.

CHOOSE

액세스되는 테이블 중 적어도 하나에 통계정보가 있다면 CBO, 그중에서도 all_rows 모드를 선택한다.
어느 테이블에도 통계정보가 없으면 RBO를 선택한다.
9i까지는 choose가 기본 설정이었으나 10g부터는 all_rows가 기본 옵티마이저 모드로 설정된다.
10g부터 RBO를 공식적으로 지원하지 않게 된 탓이며, 동적 샘플링 기본 레벨이 2로 바뀐 것과도 무관하지 않다.
즉, 통계정보 없는 테이블을 발견하면 무조건 동적 샘플링이 일어나기 때문에 RBO로 작동할 일이 없어진 것이다.

옵티마이저 모드 선택

일반적인 first_rows는 OLTP 환경에서, all_rows는 DW나 배치 프로그램 등에서 사용하는 옵티마이저 모드라고 알려져 있다.
하지만 요즘과 같은 웹 애플리케이션 환경에서는 OLTP이더라도 대개 all_rows가 올바른 선택이다.
애플리케이션에서 수행되는 쿼리 자체가 전체범위처리를 요구하기 때문이다.
all_rows 모드는 SQL 결과 집합을 모두 Fetch하기에 가장 효율적인 실행계획을 옵티마이저에게 요구하는 것이고, first_rows는 그 중 일부만 Fetch하고 멈추는 것을 전제로 가장 효율적인 실행계획을 요구하는 옵티마이저 모드다.
따라서 DW 시스템 또는 배치 프로그램이라면 all_rows 모드를 선택하는 것이 당연하게 느껴진다.
애플리케이션 특성상 확실히 first_rows가 적합하다는 판단이 서지 않는다면 all_rows를 기본 모드로 선택하고, 필요한 쿼리 또는 세션 레벨에서 first_rows 모드로 전환할 것을 권고한다.

문서에 대하여


# 이 문서는 오라클클럽에서 작성하였습니다.
# 이 문서를 다른 블로그나 홈페이지에 게재하실 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^
# 출처 : http://www.gurubee.net/pages/viewpage.action?pageId=6259368&
09 26, 2010 23:23 09 26, 2010 23:23

Trackback URL : http://develop.sunshiny.co.kr/trackback/546

Leave a comment


Recent Posts

  1. EditPlus - 문자열 검색및 변경 팁
  2. Unix - 압축 파일 내용 조회및 풀기
  3. Java - Class 버전 확인
  4. Linux - Telnet 서비스 비활성및 실행
  5. NT - 서버 원격데스크탑 연결

Recent Comments

  1. 네. 고맙습니다^^ 행복한 한해 보... sunshiny 01 16,
  2. sunshiny님. 안녕하세요... 올려 주... yihans 01 16,
  3. 답글 주셔서 고맙습니다^^ 소스 복... sunshiny 01 11,
  4. 관리자만 볼 수 있는 댓글입니다. 비밀방문자 01 11,
  5. 넵 답변감사합니다^^ 좋은 하루 되... 노로링

Recent Trackbacks

  1. 윈도우 cmd 명령어 팁 월풍도원(月風道院) - Delight on th... %M
  2. 파일 압축 Like RadioHead %M
  3. Mysql - mysql 설치후 Character set... 멀고 가까움이 다르기 때문 %M

Calendar

«   02 2012   »
      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      

Bookmarks

  1. 위키피디아
  2. MysqlKorea
  3. Oracle All Documentation
  4. 엑셈
  5. 오라클 클럽
  6. 네이버개발자센터
  7. API - Java
  8. API - Spring
  9. Java Community
  10. Reference - Spring
  11. 스프링사용자
  12. 자바소스
  13. 자바지기
  14. Ready System
  15. Solaris Freeware
  16. Linux-Site
  17. RedHat Korea
  18. 윈디하나의 솔라나라

Site Stats

TOTAL 222017 HIT
TODAY 45 HIT
YESTERDAY 312 HIT