h1.옵티마이저 행동에 영향을 미치는 요소

옵티마이저의 행동의 차이는 실행계획의 차이를 말하며, 영향을 미치는 결과는 아래와 같다.

SQL과 연산자 형태
{code:sql}

-- 연산자에 따른 변화
CREATE INDEX BSHMAN_IDX ON BSHMAN_TEST(COL1,COL3));

SELECT *
FROM BSHMAN_TEST
WHERE COL1 < 100
AND COL3 < 100

Plan hash value: 76087578























---

IdOperationNameRowsBytesCost (%CPU)Time























---

0SELECT STATEMENT4 (100)
1TABLE ACCESS BY INDEX ROWIDBSHMAN_TEST1124 (0)00:00:01
  • 2
INDEX RANGE SCANBSHMAN_IDX13 (0)00:00:01























---

Predicate Information (identified by operation id):













---

2 - access("COL1"<100 AND "COL3"<100)
filter("COL3"<100)

SELECT *
FROM BSHMAN_TEST
WHERE COL1 <> 100 <-- 연산자를 변경
AND COL3 < 100

Plan hash value: 1554795123





















-

IdOperationNameRowsBytesCost (%CPU)Time





















-

0SELECT STATEMENT8304 (100)
  • 1
TABLE ACCESS FULLBSHMAN_TEST991K11M8304 (2)00:01:40





















-

Predicate Information (identified by operation id):













---

1 - filter(("COL3"<100 AND "COL1"<>100))

||
||인덱스 , IOT, 클러스터링, 파티셔닝, MV 등 옵티마이징 팩터
{code:sql}
   CREATE INDEX BSHMAN_IDX ON BSHMAN_TEST(COL1,COL3));
      
   SELECT * 
   FROM BSHMAN_TEST
   WHERE COL1 < 100
     AND COL3 < 100

Plan hash value: 76087578
 
-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |       |       |     4 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| BSHMAN_TEST |     1 |    12 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | BSHMAN_IDX  |     1 |       |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

DROP INDEX BSHMAN_IDX;

   SELECT * 
   FROM BSHMAN_TEST
   WHERE COL1 < 100
     AND COL3 < 100

Plan hash value: 1554795123
 
---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |       |       |  8295 (100)|          |
|*  1 |  TABLE ACCESS FULL| BSHMAN_TEST |     1 |    12 |  8295   (2)| 00:01:40 |
---------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter(("COL1"<100 AND "COL3"<100))
 


||

제약 설정 : PK, FK, NOT NULL, CHECK
{code:sql}

--PK 제약과 옵티마이저
select sum(주문수량), sum(주문금액), count(*), count(distinct 고객번호)
from 주문
where 고객번호 in (select 고객번호
from 고객
where 가입일자 >= trunc(add_months(sysdate, -12)))
and 주문일자 >= trunc(add_months(sysdate, -1))

--FK 제약과 옵티마이저 : 4장 6절 참고

--Not Null 제약과 옵티마이저
제약조건으로 not null을 설정하지 않으면 옵티마이저는 null 값의 입력여부에 대한 가능성을 염두하고 실행계획을 수립.
그러므로, 테이블 전체 스캔처리한다.

SQL> desc emp
이름 널? 유형











-







---

EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)

select deptno, count(*) from emp group by deptno;



















---

IdOperationNameRowsBytesCost (%CPU)Time



















---

0SELECT STATEMENT16 (100)
1HASH GROUP BY1418216 (7)00:00:01
2TABLE ACCESS FULLEMP1418215 (0)00:00:01



















---

SQL> desc emp
이름 널? 유형











-








EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NOT NULL NUMBER(2)

select deptno, count(*) from emp group by deptno;





















---

IdOperationNameRowsBytesCost (%CPU)Time





















---

0SELECT STATEMENT1 (100)
1SORT GROUP BY NOSORT141821 (0)00:00:01
2INDEX FULL SCANDEPTNO_IDX141821 (0)00:00:01





















---

--Check 제약과 옵티마이저

– alter 하지않을경우 Filter 로 풀리지않는다.

select * from emp where sal > 5000



















--

IdOperationNameRowsBytesCost (%CPU)Time



















--

0SELECT STATEMENT15 (100)
  • 1
TABLE ACCESS FULLEMP18715 (0)00:00:01



















--

Predicate Information (identified by operation id):













---

1 - filter("SAL">5000)

-- 추가시작






























IdOperationNameStartsE-RowsE-BytesCost (%CPU)E-TimeA-RowsA-TimeBuffers






























  • 1
TABLE ACCESS FULLEMP11873 (0)00:00:01000:00:00.017






























-- 추가끝

alter table emp modify sal check (sal <= 5000);

select * from emp where sal > 5000



















---

IdOperationNameRowsBytesCost (%CPU)Time



















---

0SELECT STATEMENT1 (100)
  • 1
FILTER
  • 2
TABLE ACCESS FULLEMP18715 (0)00:00:01



















---

Predicate Information (identified by operation id):













---

1 - filter(NULL IS NOT NULL)
2 - filter("SAL">5000)

-- 추가시작



























---

IdOperationNameStartsE-RowsE-BytesCost (%CPU)E-TimeA-RowsA-Time



























---

  • 1
FILTER1000:00:00.01
  • 2
TABLE ACCESS FULLEMP018715 (0)00:00:01000:00:00.01



























---

-- 추가끝

||
||옵티마이저 힌트
{code:sql}

힌트는 매우 강력한 옵티마이저를 가지고노는 기능중에 하나이다.
아래는 왠만해서는 힌트가 우선적이지만, 아닌경우에 대해서 나열했다.
1.문법적으로 맞지않은 힌트 기술
ex) /* index */ 라든지, /*+ index /*....
2.잘못된 참조사용
ex) 존재하지않은 인덱스명이나 alias에 힌트를 주는경우...
3.의미적으로 맞지 않게 힌트를 기술
ex)서브쿼리에 unnext와 push_subq를 같이 기술하는 경우(unnest되지 않은 서브쿼리만이 push_subq 힌트적용대상)
4.논리적으로 불가능한 액세스 경로
ex)select /*+ index(e emp_ename) */ count(*) from emp 3
5.버그

create table t1 ( a number, b varchar2(100));
create table t2 ( a number, b varchar2(100));
create table t3 ( a number, b varchar2(100));
create table t4 ( a number, b varchar2(100));
create table t5 ( a number, b varchar2(100));

alter system flush shared_pool;

set timing on;

SQL> declare
  2   i_cnt number;
  3  begin
  4    for i in 1..10000
  5
  6    loop
  7
  8    execute immediate 'select /*+ ordered */ count(*)'||
  9                      ' from t1,t2,t3,t4,t5        '||
 10                      'where t1.a = ' || i ||
 11                      '  and t2.a = ' || i ||
 12                      '  and t3.a = ' || i ||
 13                      '  and t4.a = ' || i ||

 14                      '  and t5.a = ' || i into i_cnt;
 15    end loop;
 16  end;
 17  /

PL/SQL 처리가 정상적으로 완료되었습니다.

경   과: 00:00:06.87


PL/SQL 처리가 정상적으로 완료되었습니다.

경   과: 00:00:19.89
SQL> declare
  2   i_cnt number;
  3  begin
  4    for i in 1..10000
  5
  6    loop
  7
  8    execute immediate 'select  count(*)'||
  9                      ' from t1,t2,t3,t4,t5        '||
 10                      'where t1.a = ' || i ||
 11                      '  and t2.a = ' || i ||
 12                      '  and t3.a = ' || i ||
 13                      '  and t4.a = ' || i ||
 14                      '  and t5.a = ' || i into i_cnt;
 15    end loop;
 16  end;
 17  /

PL/SQL 처리가 정상적으로 완료되었습니다.

경   과: 00:00:23.50

Ordered 힌트를 명시하면 옵티마이저는 모든 조인 순서를 고려하지 않고 사용자가 지정한 순서로만
실행계획 후보근을 선정하여 계산하므로 빠르고 최적화를 완료했다.

옵티마이저 힌트에 관한 일반적인 사용원칙은
1. 가급적 힌트를 사용을 자제하고, 옵티마이저가 스스로 좋은 선택을 할 수 있도록 돕는다.
2. 옵티마이저가 잘못된 선택을 할 ?만 힌트를 사용한다

한마디로 옵티마이저를 믿고 아니다싶으면 조작해라..


||

통계정보 : 오브젝트 통계, 시스템 통계
{code:sql}

통계정보는 옵티마이저에게 절대적인 영향을 미치는 요소중에 하나이다.
자세한 내용은 4절과, 8절에 나온다.

||
||옵티마이저 관련 파라미터
{code:sql}

select name, value, isdefault, default_value
from v$sys_optimizer_env;


NAME                                     VALUE                     ISD DEFAULT_VALUE
---------------------------------------- ------------------------- --- -------------------------
parallel_execution_enabled               true                      YES true
optimizer_features_enable                10.2.0.1                  YES 10.2.0.1
cpu_count                                8                         YES 8
active_instance_count                    1                         YES 1
parallel_threads_per_cpu                 2                         YES 2
hash_area_size                           131072                    YES 131072
bitmap_merge_area_size                   1048576                   YES 1048576
sort_area_size                           65536                     YES 65536
sort_area_retained_size                  0                         YES 0
pga_aggregate_target                     198656 KB                 YES 198656 KB

parallel_query_mode                      enabled                   YES enabled

NAME                                     VALUE                     ISD DEFAULT_VALUE
---------------------------------------- ------------------------- --- -------------------------
parallel_dml_mode                        disabled                  YES disabled
parallel_ddl_mode                        enabled                   YES enabled
optimizer_mode                           all_rows                  YES all_rows
cursor_sharing                           exact                     YES exact
star_transformation_enabled              false                     YES false
optimizer_index_cost_adj                 100                       YES 100
optimizer_index_caching                  0                         YES 0
query_rewrite_enabled                    true                      YES true
query_rewrite_integrity                  enforced                  YES enforced
workarea_size_policy                     auto                      YES auto
optimizer_dynamic_sampling               2                         YES 2

NAME                                     VALUE                     ISD DEFAULT_VALUE
---------------------------------------- ------------------------- --- -------------------------
statistics_level                         typical                   YES typical
skip_unusable_indexes                    true                      YES true
optimizer_secure_view_merging            true                      YES true


오라클을 업그레이드로 인하여 발생되는 문제를 원치않는다면 
optimizer_features_enable 파라미터를 이전버전으로 설정 하면된다.

alter system set optimizer_features_enable = "9.2.0.4";

||

DBMS 버전과 종류
{code:sql}

select max(empno) from emp

-- 위와같은 SQL을 오라클 7버전 까지는 최적화하려고 아래와같이 만들어서 사용

select /*+ index_desc(emp emp_pk) */ empno from emp
where rownum =1






























-

IdOperationNameStartsE-RowsE-BytesCost (%CPU)E-TimeA-RowsA-TimeBuffers






























-

  • 1
COUNT STOPKEY1100:00:00.011
2INDEX FULL SCANPK_EMP1141821 (0)00:00:01100:00:00.011






























-

-- 오라클 8버전부터는 index_desc 힌트를 사용하지않아도 최적화처리
































---

IdOperationNameStartsE-RowsE-BytesCost (%CPU)E-TimeA-RowsA-TimeBuffers
































---

1SORT AGGREGATE1113100:00:00.011
2INDEX FULL SCAN (MIN/MAX)PK_EMP1141821 (0)00:00:01100:00:00.011
































---

– DBMS 종류에 따라 실행계획확인
쿼리 : select min(empno) mx, max(empno) mn from emp

오라클은 index_desing 형태로 나와야되는거같지만 index full scan을 선택한다.

mssql에서는 정상적으로 작동하고있다.

||