-- 연산자에 따른 변화
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 |
-------------------------------------------------------------------------------------------
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
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 8304 (100)| |
|* 1 | TABLE ACCESS FULL| BSHMAN_TEST | 991K| 11M| 8304 (2)| 00:01:40 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(("COL3"<100 AND "COL1"<>100))
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 제약과 옵티마이저
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;
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 16 (100)| |
| 1 | HASH GROUP BY | | 14 | 182 | 16 (7)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 14 | 182 | 15 (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;
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1 (100)| |
| 1 | SORT GROUP BY NOSORT| | 14 | 182 | 1 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | DEPTNO_IDX | 14 | 182 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------
--Check 제약과 옵티마이저
-- alter 하지않을경우 Filter 로 풀리지않는다.
select * from emp where sal > 5000
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 15 (100)| |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 87 | 15 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SAL">5000)
-- 추가시작
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------------------
|* 1 | TABLE ACCESS FULL| EMP | 1 | 1 | 87 | 3 (0)| 00:00:01 | 0 |00:00:00.01 | 7 |
--------------------------------------------------------------------------------------------------------------------
-- 추가끝
alter table emp modify sal check (sal <= 5000);
select * from emp where sal > 5000
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1 (100)| |
|* 1 | FILTER | | | | | |
|* 2 | TABLE ACCESS FULL| EMP | 1 | 87 | 15 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(NULL IS NOT NULL)
2 - filter("SAL">5000)
-- 추가시작
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time |
-----------------------------------------------------------------------------------------------------------
|* 1 | FILTER | | 1 | | | | | 0 |00:00:00.01 |
|* 2 | TABLE ACCESS FULL| EMP | 0 | 1 | 87 | 15 (0)| 00:00:01 | 0 |00:00:00.01 |
-----------------------------------------------------------------------------------------------------------
-- 추가끝
-- 힌트는 매우 강력한 옵티마이저를 가지고노는 기능중에 하나이다.
-- 아래는 왠만해서는 힌트가 우선적이지만, 아닌경우에 대해서 나열했다.
-- 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. 옵티마이저가 잘못된 선택을 할 ?만 힌트를 사용한다
한마디로 옵티마이저를 믿고 아니다싶으면 조작해라..
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";
select max(empno) from emp
-- 위와같은 SQL을 오라클 7버전 까지는 최적화하려고 아래와같이 만들어서 사용
select /*+ index_desc(emp emp_pk) */ empno from emp
where rownum =1
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------------------------------
|* 1 | COUNT STOPKEY | | 1 | | | | | 1 |00:00:00.01 | 1 |
| 2 | INDEX FULL SCAN| PK_EMP | 1 | 14 | 182 | 1 (0)| 00:00:01 | 1 |00:00:00.01 | 1 |
---------------------------------------------------------------------------------------------------------------------
-- 오라클 8버전부터는 index_desc 힌트를 사용하지않아도 최적화처리
-------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------------------------
| 1 | SORT AGGREGATE | | 1 | 1 | 13 | | | 1 |00:00:00.01 | 1 |
| 2 | INDEX FULL SCAN (MIN/MAX)| PK_EMP | 1 | 14 | 182 | 1 (0)| 00:00:01 | 1 |00:00:00.01 | 1 |
-------------------------------------------------------------------------------------------------------------------------------
-- DBMS 종류에 따라 실행계획확인
-- 쿼리 : select min(empno) mx, max(empno) mn from emp
-- 오라클은 index_desing 형태로 나와야되는거같지만 index full scan을 선택한다.
-- mssql에서는 정상적으로 작동하고있다.
- 강좌 URL : http://www.gurubee.net/lecture/3276
- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^
- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.