정미나닷컴
[Oracle] 오라클 인덱스 설계 본문
인덱스 설계 전략
- 조건절에 항상 사용되거나, 자주 등장하는 컬럼들을 선정
- '=' 조건으로 자주 조회되는 컬럼들을 앞쪽에 위치
[검색조건 1]
WHERE 고객번호 = '0000001'
AND 거래일자 BETWEEN '20150101' AND '20171001';
[검색조건 2]
WHERE 상품번호 = '1'
AND 거래일자 BETWEEN '20150101' AND '20171001';
[검색조건 3]
WHERE 고객번호 = '0000001'
AND 상품번호 = '1'
AND 거래일자 BETWEEN '20150101' AND '20171001';
[검색조건 4]
WHERE 거래일자 BETWEEN '20150101' AND '20171001';
1) 일단 '=' 조건으로 자주 등장하는 컬럼은 고객번호와 상품번호인데 고객번호는 100만 건, 상품번호는 10만 건이니 우선순위로 볼 때 고객번호가 선두에 놓여야 효율적일 확률이 높음
2) 인덱스 개수는 불가피한 경우를 제외하고는 많이 만들지 않는게 좋으므로 2개 ~ 3개로 한정
3) 내 마음속의 후보들
- 후보1 IX1[고객번호+거래일자+상품번호] / IX2[상품번호+거래일자] / IX3[거래일자]
- 후보2 IX1[고객번호+거래일자+상품번호] / IX2[거래일자] => 사실 상 후보1에서 상품번호+거래일자만 제외한 것임
4) Let's 실습!!
SELECT LPAD(TRIM(TO_CHAR(ROWNUM)),7,'0') CUST_NO,
DECODE(MOD(ROWNUM,10),0,ROWNUM/10,TRUNC(ROWNUM/10)+1) GOODS_CODE,
'GOODS'||ROWNUM GOODS_NAME,
SYSDATE-ROUND(DBMS_RANDOM.VALUE(10, 1000),0) DEAL_DATE
FROM DUAL
CONNECT BY LEVEL <= 1000000 ;
※ SELECT 절은 모든 컬럼을 가져오도록 했으며 실제로 그럴 일은 없겠지만 고객 한 명당 한 건의 거래 내역만 존재한다고 가정함
[검색조건 1]
- 후보1과 후보2 모두 IX1[CUST_NO, DEAL_DATE, GOODS_CODE] 이용
--------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 |00:00:00.01 | 4 |
|* 1 | FILTER | | 1 | 1 |00:00:00.01 | 4 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEAL | 1 | 1 |00:00:00.01 | 4 |
|* 3 | INDEX RANGE SCAN | IX1 | 1 | 1 |00:00:00.01 | 3 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_DATE('20150101')<=TO_DATE('20171001'))
3 - access("CUST_NO"='0000001' AND "DEAL_DATE">='20150101' AND
"DEAL_DATE"<='20171001')
▶ INDEX RANGE SCAN, 비효율 거의 없음
[검색조건 2]
- 후보1
--------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 |00:00:00.01 | 4 |
|* 1 | FILTER | | 1 | 10 |00:00:00.01 | 4 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEAL | 1 | 10 |00:00:00.01 | 4 |
|* 3 | INDEX RANGE SCAN | IX2 | 1 | 10 |00:00:00.01 | 3 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_DATE('20150101')<=TO_DATE('20171001'))
3 - access("GOODS_CODE"=1 AND "DEAL_DATE">='20150101' AND "DEAL_DATE"<='20171001')
▶ IX2[GOODS_CODE, DEAL_DATE]를 이용하여 INDEX RANGE SCAN, 비효율 거의 없음
- 후보2
--------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 |00:00:00.05 | 4314 |
|* 1 | FILTER | | 1 | 10 |00:00:00.05 | 4314 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEAL | 1 | 10 |00:00:00.05 | 4314 |
|* 3 | INDEX FULL SCAN | IX1 | 1 | 10 |00:00:00.05 | 4313 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_DATE('20150101')<=TO_DATE('20171001'))
3 - access("DEAL_DATE">='20150101' AND "GOODS_CODE"=1 AND
"DEAL_DATE"<='20171001')
filter(("GOODS_CODE"=1 AND "DEAL_DATE">='20150101' AND
"DEAL_DATE"<='20171001'))
▶ 마땅한 인덱스가 없어 IX1[CUST_NO, DEAL_DATE, GOODS_CODE]를 이용, GOODS_CODE가 선두 컬럼이 아닌 관계로 INDEX FULL SCAN을 했으며 이로 인해 메모리에서 읽은 block 수가 적진 않음 (10 Rows를 읽기 위해 4314개의 block 스캔)
[검색조건 3]
- 후보1과 후보2 모두 IX1[CUST_NO, DEAL_DATE, GOODS_CODE] 이용
--------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 |00:00:00.01 | 4 |
|* 1 | FILTER | | 1 | 1 |00:00:00.01 | 4 |
| 2 | TABLE ACCESS BY INDEX ROWID| DEAL | 1 | 1 |00:00:00.01 | 4 |
|* 3 | INDEX RANGE SCAN | IX1 | 1 | 1 |00:00:00.01 | 3 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_DATE('20150101')<=TO_DATE('20171001'))
3 - access("CUST_NO"='0000001' AND "DEAL_DATE">='20150101' AND
"GOODS_CODE"=1 AND "DEAL_DATE"<='20171001')
filter("GOODS_CODE"=1)
▶ GOODS_CODE가 '=' 조건임에도 선행 컬럼인 DEAL_DATE가 범위 조건인 관계로 필터 조건으로 빠짐, 그럼에도 불구하고 선두 컬럼인 CUST_NO의 변별력이 막강하여 비효율 거의 없음
[검색조건 4]
- 후보1과 후보2 모두 [DEAL_DATE] 인덱스 이용
(이 쿼리는 XPlan으로 보려니 자꾸 COLLECTION ITERATOR PICKLER FETCH 가 나옴
FETCH 수가 너무 많아서 그런가 하고 검색 범위를 줄여도 마찬가지.. (아시는 분 댓글 좀 ㅠㅠ)
그래서 Sql-Developer 실행 계획으로 확인)
▶ INDEX RAGNE SCAN 이용, 비효율은 거의 없음
☞ [검색조건 2]와 같은 쿼리가 자주 수행된다면 후보1 인덱스가 더 적합하다고 볼 수 있음
* 그밖의 좋은 인덱스 후보
IX1[고객번호+거래일자] / IX2[거래일자+상품번호+고객번호]
▶ 자주 입력되는 거래일자 범위가 아주 넓지만 않다면 성능이 그다지 나쁘지 않음
인덱스 설계 시 시스템 전체적인 관점에서 고려해야 할 요소
- 쿼리 수행 빈도
- 업무상 중요도
- 클러스터링 팩터
- 데이터량
- DML 부하(= 기존 인덱스 개수, 초당 DML 발생량, 자주 갱신되는 컬럼 포함 여부 등)
- 저장 공간
- 인덱스 관리 비용
결합 인덱스 컬럼 순서 결정 시, 선택도 이슈
- '=' 조건으로 항상 사용되는 컬럼들을 앞쪽에 위치시켰다면, 그 중 선택도가 낮은 것을 앞쪽에 두려는 노력은 의미가 없는 것이거나 오히려 손해일 수 있음
- 선택도가 높은 컬럼이 선두에 있을 경우 선두 컬럼이 조건절에서 빠졌을 때 INDEX SKIP SCAN을 이용하거나 IN-List로 튜닝 가능 (단, 선두 컬럼의 Distinct Value 개수가 충분히 적어야 함)
- 선택도가 낮은 컬럼이 선두에 있을 경우 인덱스 중간에 범위검색 조건이 와도 비효율이 크지 않음 (ex: 고객번호 + 거래일자 + 상품번호)
소트 오퍼레이션을 생략하기 위한 컬럼 추가
CREATE INDEX T_IDX ON T(A, B, C, D);
▼
SELECT * FROM T WHERE A = 1 ORDER BY A, B, C;
SELECT * FROM T WHERE A = 1 AND B = 1 ORDER BY C, D;
SELECT * FROM T WHERE A = 1 AND C = 1 ORDER BY B, D;
SELECT * FROM T WHERE A = 1 AND B = 1 ORDER BY A, B, C, D;
=> 모두 ORDER BY 소트 오퍼레이션이 생략됨
* 인덱스를 이용한 소트 오퍼레이션 대체가 불가능한 경우
SELECT * FROM T WHERE A = 1 ORDER BY C;
SELECT * FROM T
WHERE A = 1
AND B BETWEEN 1 AND 2
ORDER BY C, D;
SELECT * FROM T
WEHRE A = 1
AND B BETWEEN 1 AND 2
ORDER BY A, C, B;
'IT' 카테고리의 다른 글
[Oracle] 오라클 스칼라 서브쿼리를 이용한 JOIN (1) | 2017.10.29 |
---|---|
[Oracle] 오라클 JOIN 순서의 중요성 (0) | 2017.10.29 |
[Oracle] 오라클 인덱스 구조 - Bitmap Index, 비트맵 인덱스 (0) | 2017.10.22 |
[Oracle] 오라클 HWM (High Water Mark) (0) | 2017.10.22 |
[Oracle] 오라클 인덱스 단편화, Index Fragmentation (0) | 2017.10.22 |