Upload
3-2
View
905
Download
0
Embed Size (px)
Citation preview
온라인 화상(양방향) 교육 2회차
(2016.12.15 목요일 20:30~21:00)
- 복합인덱스 스캐닝과 관련된 힌트구문
- INDEX JOIN개요 및 관련힌트구문
INDEX 힌트와 복합인덱스
인자로 INDEX 힌트에 인덱스명이 아닌 칼럼이 출현하면 해당 칼럼을
포함하고 있는 인덱스를 이용하라는 의미이다.
[형식]
/*+ INDEX(테이블명 (칼럼명, 칼럼명,,,)) */
-- 실습 테이블 생성
SQL> CREATE TABLE INDEXTEST (
A1 NUMBER NOT NULL,
A2 NUMBER NOT NULL,
A3 VARCHAR2(50) NOT NULL,
A4 VARCHAR2(100));
-- 100만건 생성
SQL> INSERT INTO INDEXTEST
SELECT
MOD(ROWNUM-1, 90) * 4 A1,
ROWNUM - 1 A2,
TO_CHAR(ROWNUM - 1, 'RN') A3,
LPAD('A',100,'A') A4
FROM
DUAL
CONNECT BY
LEVEL<=1000000;
SQL> COMMIT;
-- 실습을 위한 인덱스 생성
SQL> CREATE INDEX IDX_IT_1_2 ON INDEXTEST(A1,A2);
SQL> CREATE INDEX IDX_IT_2_1_3 ON INDEXTEST(A2,A1,A3);
SQL> CREATE INDEX IDX_IT_3_1_2 ON INDEXTEST(A3,A1,A2);
-- INDEXTEST 테이블의 통계정보 생성
SQL> EXEC
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'INDEXTEST',
CASCADE=>TRUE);
-- 먼저 힌트를 사용하지 않은 쿼리를 보자.
-- Full TableScan을 하지않고 A2, A1, A3 복합 인덱스를 사용한다.
SQL> SELECT A1, A2, A3 FROM INDEXTEST
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 23M| 1336 (1)|
00:00:17 |
| 1 | INDEX FAST FULL SCAN| IDX_IT_2_1_3 | 1000K| 23M| 1336 (1)|
00:00:17 |
-------------------------------------------------------------------------------------
-- 이번에는 A1, A2 칼럼에 있는 인덱스를 사용하라는 힌트를 줘보자.
-- A1, A2 복합인덱스를 이용한다.
SQL> SELECT /*+ index(INDEXTEST (A1, A2)) */ A1, A2, A3 FROM INDEXTEST;
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 23M| 1003K
(1)| 03:20:40 |
| 1 | TABLE ACCESS BY INDEX ROWID| INDEXTEST | 1000K| 23M|
1003K (1)| 03:20:40 |
| 2 | INDEX FULL SCAN | IDX_IT_1_2 | 1000K| | 2755 (1)|
00:00:34 |
------------------------------------------------------------------------------------------
-- 이번엔 A2, A1 칼럼 인덱스를 사용하라는 힌트를 줘보자.
SQL> SELECT /*+ index(INDEXTEST (A2, A1)) */ A1, A2, A3 FROM INDEXTEST;
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 23M| 4905 (1)|
00:00:59 |
| 1 | INDEX FULL SCAN | IDX_IT_2_1_3 | 1000K| 23M| 4905 (1)| 00:00:59
|
---------------------------------------------------------------------------------
힌트를 사용하지 않은 쿼리보다 COST가 더 높게 나온다.
-- 이번엔 A3, A1, A2 칼럼 인덱스를 사용하라는 힌트를 줘보자.
-- A3, A1, A2 복합인덱스를 이용한다.
SQL> SELECT /*+ index(INDEXTEST (A3, A1, A2)) */ A1, A2, A3 FROM INDEXTEST;
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 23M| 5188 (1)|
00:01:03 |
| 1 | INDEX FULL SCAN | IDX_IT_3_1_2 | 1000K| 23M| 5188 (1)| 00:01:03
|
---------------------------------------------------------------------------------
-- 이번에는 A1, A2 칼럼 인덱스를 사용하는데 A2 칼럼에 조건을 줘보자.
-- 힌트구안에 A1, A2가 기술되어 있으므로 IDX_IT_1_2 인덱스를 이용하여
-- A1칼럼을 SKIP하면서 A2조건을 확인하는 Index skip Scanning 연산을 한다.
SQL> SELECT /*+ index(INDEXTEST (A1, A2)) */ A1, A2, A3 FROM INDEXTEST
WHERE A2 < 10;
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 250 | 103 (0)|
00:00:02 |
| 1 | TABLE ACCESS BY INDEX ROWID| INDEXTEST | 10 | 250 | 103 (0)|
00:00:02 |
|* 2 | INDEX SKIP SCAN | IDX_IT_1_2 | 10 | | 92 (0)|
00:00:02 |
------------------------------------------------------------------------------------------
-- 앞 예문에서 A1, A2의 순서만 바꾸면 IDX_IT_2_1_3을 이용하여
-- INDEX RANGE SCAN 한다.
SQL> SELECT /*+ index(INDEXTEST (A2, A1)) */ A1, A2, A3
FROM INDEXTEST
WHERE A2 < 10;
-- 이번엔 A1, A2, A3 칼럼의 인덱스를 사용하라는 힌트를 줘보자.
-- IDX_IT_1_2 인덱스에는 A3 칼럼이 없으므로 사용하지 않고, WHERE절에 A2칼
럼이 출현했으므로 IDX_IT_2_1_3 인덱스를 사용했다.
SQL> SELECT /*+ index(INDEXTEST (A1, A2, A3)) */ A1, A2, A3 FROM INDEXTEST
WHERE A2 < 10;
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 250 | 3 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IDX_IT_2_1_3 | 10 | 250 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------
-- A1, A2, A3순으로 인덱스를 생성하자.
-- A2 칼럼으로 WHERE절에 비교했지만 A3칼럼이 SELECT 리스트에 있고
IDX_IT_1_2_3인덱스가
-- 있으므로 이 인덱스를 이용하여 SKIP SCANNING 한다.
SQL> CREATE INDEX IDX_IT_1_2_3 ON INDEXTEST(A1,A2,A3);
-- A1, A2, A3 순서대로 생성된 IDX_IT_1_2_3 인덱스가 있고
-- A2 칼럼으로 WHERE절에 비교했으므로 IDX_IT_1_2_3인덱스 이용하여 SKIP
SCANNING
-- 만약 IDX_IT_1_2_3 인덱스가 없다면 IDX_IT_2_1_3 인덱스를 이용할 것이다.
SQL> SELECT /*+ INDEX(INDEXTEST (A1 A2 A3)) */ A1,A2,A3 FROM INDEXTEST
WHERE A2 < 10; ------------------------------------------------------------------------
---------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 250 | 92 (0)| 00:00:02 |
|* 1 | INDEX SKIP SCAN | IDX_IT_1_2_3 | 10 | 250 | 92 (0)| 00:00:02 |
---------------------------------------------------------------------------------
-- 아래의 경우 A1, A2 칼럼의 인덱스를 쓰라는 힌트인데
-- A3 칼럼 때문에 TABLE ACCESS(BY INDEX ROWID)연산을 한다.
SQL> SELECT /*+ INDEX(INDEXTEST (A1 A2)) */ A1,A2,A3 FROM INDEXTEST
WHERE A2 < 10;
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 250 | 103 (0)|
00:00:02 |
| 1 | TABLE ACCESS BY INDEX ROWID| INDEXTEST | 10 | 250 | 103 (0)|
00:00:02 |
|* 2 | INDEX SKIP SCAN | IDX_IT_1_2 | 10 | | 92 (0)|
00:00:02 |
------------------------------------------------------------------------------------------
-- 아래의 경우 A1 칼럼의 인덱스를 쓰라는 힌트인데
-- WHERE절에 A2 칼럼이 출현했으므로 가능하면 A1이 선두칼럼에 있는 인덱스
를 선택할 것이고, A3 칼럼이 SELECT 리스트에 출현했으므로 IDX_IT_1_2_3 인덱
스를 사용한다.
SQL> SELECT /*+ INDEX(INDEXTEST (A1)) */ A1,A2,A3 FROM INDEXTEST
WHERE A2 < 10;
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 250 | 92 (0)| 00:00:02 |
|* 1 | INDEX SKIP SCAN | IDX_IT_1_2_3 | 10 | 250 | 92 (0)| 00:00:02 |
---------------------------------------------------------------------------------
----------------------------------------------------------------------------------
-- myemp1(1000 만건) 이용한 실습
----------------------------------------------------------------------------------
-- deptno, job, sal를 이용한 복합 인덱스 생성
create index idx_myemp1_deptno_job_sal on myemp1(deptno, job, sal);
-- idx_myemp1_deptno_job_sal 인덱스이용 INDEX FULL SCAN 한다.
-- 0.6초
select /*+ index(myemp1 (deptno, job)) */ count(1)
from myemp1
where job = 'CLERK';
-- idx_myemp1_deptno_job_sal 인덱스이용 INDEX FULL SCAN 한다.
-- 0.4초, 위의 실행계획보다는 조금 이점이 있다.
select /*+ index_ss(myemp1 (deptno, job)) */ count(1)
from myemp1
where job = 'CLERK';
-- idx_myemp1_job_deptno_sal 인덱스이용 INDEX RANGE SCAN 한다.
-- 0.06초, 최적의 방법
select /*+ index(myemp1 ( job, deptno)) */ count(1)
from myemp1
where job = 'CLERK';
-- 힌트 사용안했더니 idx_myemp1_deptno_job_sal 인덱스 이용 FAST FULL
SCAN 한다.
-- 0.9초
select count(1)
from myemp1
where job in ('CLERK', 'SALESMAN')
and sal > 10000
and deptno not in ('1');
-- INDEX SKIP SCAN을 유도, 3초
select /*+ index_ss(myemp1 (deptno)) */ count(1)
from myemp1
where job in ('CLERK', 'SALESMAN')
and sal > 10000
and deptno not in ('1');
-- 부서코드로 내림차순 정렬, FULL TABLE SCAN 한다. 1.6초
select ename, deptno, job
from myemp1
where job in ('CLERK', 'SALESMAN')
and sal > 10000
and deptno not in ('0')
order by deptno desc;
-- 인덱스 영역에서 뒤쪽부터 스캐닝하면 된다. 0.2초
select /*+ index_desc(myemp1 (deptno)) */ ename, deptno, job
from myemp1
where job in ('CLERK', 'SALESMAN')
and sal > 10000
and deptno not in ('0');
-- 인덱스 스킵 스캔을 인덱스 뒷부분 부터 해보자. 0.5초
select /*+ index_ss_desc(myemp1 (deptno)) */ ename, deptno, job
from myemp1
where job in ('CLERK', 'SALESMAN')
and sal > 10000
and deptno not in ('0');
Hints For Access Paths(INDEX_JOIN)
인덱스를 조인하여 스캐닝을 유도하는 힌트로 효과적이기 위해서는 테이블에
만들어진 인덱스가 SELECT문장 리스트에 나타난 컬럼 들을 모두 가져야
한다. 테이블간 조인에 사용되는 것과 무관하게 하나의 테이블에 있는 여러
인덱스를 이용해 테이블 원본데이터 ACCESS없이 결과 집합을 만들 때
사용하는 인덱스 스캔방식 이다.
내부적으로 해시조인을 이용하며 index$_join$_xxx 의 형태로 실행계획에
표시된다.
MYEMP1 테이블의 인덱스는 PK(empno)외 SAL칼럼에 인덱스가 생성되어 있어
야 한다.
아래 쿼리로 MYEMP1 테이블의 인덱스를 확인해 보고 없다면 SAL 칼럼에 인덱
스를 생성하자.
SQL> SELECT a.index_name, a.column_name, b.visibility
FROM user_ind_columns a, user_indexes b
WHERE a.table_name = 'MYEMP1'
AND a.index_name = b.index_name
PK Unique인덱스의 이름은 PK_MYEMP1 이다.
SQL> DESC MYEMP1
이름 널? 유형
----------------------------------------------------------------------- -------- ----------
---
EMPNO NOT NULL
NUMBER
ENAME
VARCHAR2(100)
DEPTNO
VARCHAR2(1)
ADDR
VARCHAR2(100)
SAL
NUMBER(7)
JOB
VARCHAR2(20)
COMM
NUMBER(7)
SUNGBYU
VARCHAR2(1)
HIREDATE DATE
OUTDATE
VARCHAR2(8)
MGR
NUMBER
-- SAL칼럼에 인덱스가 없다면 생성하자.
SQL> create index idx_myemp1_sal on myemp1(sal);
-- 힌트없이 실행하니 전체 테이블을 FULL SCAN 한다. 7.8초
-- MYEMP1 테이블의 EMPNO인 경우 1부터 10,000,002까지 유일한 값을 가지
고 있고
-- sal의 경우 0부터 5,999,999까지의 값으로 이루어져 있다.
-- 1000만건이니 sal 값은 대략 2~3개 정도 중복되어 있다.
SQL> SELECT empno, sal FROM myemp1 e
WHERE e.empno > 8500000
AND e.sal > 1000000
ORDER BY empno DESC;
경 과: 00:00:7.8
Execution Plan
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 12 | 25258 (1)| 00:05:04 |
| 1 | SORT AGGREGATE | | 1 | 12 | |
|
|* 2 | TABLE ACCESS FULL| MYEMP1 | 1220K| 13M| 25258 (1)| 00:05:04
|
-----------------------------------------------------------------------------
-- 이번에는 index_join 힌트를 사용해 보자. 2.9초
SQL> SELECT /*+ index_join(e idx_myemp1_sal PK_MYEMP1) */
empno, sal
FROM myemp1 e
WHERE e.empno > 8500000
AND e.sal > 1000000
ORDER BY empno DESC;
경 과: 00:00:02.9
Execution Plan
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
T i m e |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 12 | 28951 (1)|
0 0 : 0 5 : 4 8 |
| 1 | SORT ORDER BY | | 1 | 12 | |
|
|* 2 | VIEW | index$_join$_001 | 1220K| 13M| 28951 (1)|
0 0 : 0 5 : 4 8 |
|* 3 | HASH JOIN | | | | |
|
|* 4 | INDEX RANGE SCAN| PK_MYEMP1 | 1220K| 13M|
4 6 8 9 4 ( 1 ) | 0 0 : 0 9 : 2 3
|* 5 | INDEX RANGE SCAN| IDX_MYEMP1_SAL | 1220K| 13M|
4 3 5 K ( 5 ) |
----------------------------------------------------------------------------------------
-- SELECT 리스트에 인덱스에 없는 컬럼을 기술하니 FULL TABLE SCAN 한다.
SQL> SELECT /*+ index_join(e idx_myemp1_sal PK_MYEMP1) */
empno, sal, sungbyul
FROM myemp1 e
WHERE e.empno > 8500000
AND e.sal > 1000000
ORDER BY empno DESC;
경 과: 00:00:7.99
Execution Plan
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 25258 (1)| 00:05:04 |
| 1 | SORT AGGREGATE | | 1 | 26 | |
|
|* 2 | TABLE ACCESS FULL| MYEMP1 | 1220K| 30M| 25258 (1)| 00:05:04
|
-----------------------------------------------------------------------------