SELECT employee_id, last_name, commission_pct, rowid,
-- 데이터 파일 번호
DBMS_ROWID.ROWID_RELATIVE_FNO(rowid) AS FILE_NO,
-- 데이터 블록 번호
DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) AS BLOCK_NO,
-- Row Number
DBMS_ROWID.ROWID_ROW_NUMBER(rowid) AS ROW_NO
FROM employees ;
172 Bates 0.15 AAAUsCAANAABgWzABR 13 394675 81
173 Kumar 0.1 AAAUsCAANAABgWzABS 13 394675 82
174 Abel 0.3 AAAUsCAANAABgWzABT 13 394675 83
175 Hutton 0.25 AAAUsCAANAABgWzABU 13 394675 84
176 Taylor 0.2 AAAUsCAANAABgWzABV 13 394675 85
177 Livingston 0.2 AAAUsCAANAABgWzABW 13 394675 86
178 Grant 0.15 AAAUsCAANAABgWzABX 13 394675 87
179 Johnson 0.1 AAAUsCAANAABgWzABY 13 394675 88
180 Taylor null AAAUsCAANAABgWzABZ 13 394675 89
181 Fleaur null AAAUsCAANAABgWzABa 13 394675 90
182 Sullivan null AAAUsCAANAABgWzABb 13 394675 91
183 Geoni null AAAUsCAANAABgWzABc 13 394675 92
184 Sarchand null AAAUsCAANAABgWzABd 13 394675 93
185 Bull null AAAUsCAANAABgWzABe 13 394675 94
...
DEFINE rid = 'AAAUsCAANAABgWzAAE';
-- 컬럼 인덱스 생성
CREATE INDEX empl_comm_ix ON employees(commission_pct) ;
-- 실행 쿼리
SELECT employee_id, last_name, commission_pct, rowid
FROM employees
WHERE commission_pct = 0.4;
-- * where 조건으로 치지 않음 : 실행계획 ID컬럼에 *문자 미포함
-- WHERE rowid = chartorowid('&rid') ;
-- 실제 실행 계획 확인
SELECT *
FROM table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST')) ;
SQL_ID 892vwpghpw26u, child number 0
-------------------------------------
SELECT employee_id, last_name, commission_pct, rowid FROM employees
-- where 조건으로 치지 않음 : 실행계획 ID컬럼에 *문자 미포함 WHERE commission_pct = 0.4
Plan hash value: 337358348
--------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 2 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES | 1 | 5 | 1 |00:00:00.01 | 2 |
|* 2 | INDEX RANGE SCAN | EMPL_COMM_IX | 1 | 5 | 1 |00:00:00.01 | 1 |
--------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("COMMISSION_PCT"=.4)
모든 SQL 문장이 실행될 때 항상 인덱스를 사용하는 것이 올바른 실행 계획은 아니다.
전체의 데이터 중에 많은 양의 행을 검색하는 경우 Full Table Scan이 효율적이며 소량의 데이터를 검색할 경우
Index Scan이 Full Table Scan 보다 I/O를 줄일 수 있는 실행 계획이 된다.
항상 Index Scan이 가능한 실행 계획을 사용하는 것보다 전체의 데이터 중 얼마만큼의 행을
검색하는지를 확인하여 Index의 사용 유무를 결정하는 것이 더 중요하다.
-- 힌트가 없는 테이블 조회 쿼리
SELECT cust_id, COUNT(*)
FROM sales s
WHERE channel_id = 3
GROUP BY cust_id ;
-- 테이블 풀스캔 힌트 사용 쿼리
SELECT /*+ full(s) */ cust_id, COUNT(*)
FROM sales s
WHERE channel_id = 3
GROUP BY cust_id ;
-- 컬럼 인덱스 힌트 사용 쿼리
SELECT /*+ index(s(channel_id)) */
cust_id, COUNT(*)
FROM sales s
WHERE channel_id = 3
GROUP BY cust_id ;
실행 계획 조회 결과 테이블 풀스캔 사용 쿼리의 경우가 버퍼 I/O 횟수가 더 많다
따라서 인덱스를 무조건 사용해야만 하는 것은 아니다.
쿼리 튜닝의 시작은 실제 쿼리 실행 계획 확인하고 리소스의 사용과 응답 시간을 고려하는 데서 시작된다.
// 테이블 풀스캔 사용 쿼리의 실행 계획
--------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 200 |00:00:00.05 | 4438 |
| 1 | HASH GROUP BY | | 1 | 7059 | 200 |00:00:00.05 | 4438 |
|* 2 | TABLE ACCESS FULL| SALES | 1 | 540K| 540K|00:00:00.02 | 4438 |
--------------------------------------------------------------------------------------
// 컬럼 인덱스 힌트 사용 쿼리의 실행 계획
-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 200 |00:00:00.30 | 5480 |
| 1 | HASH GROUP BY | | 1 | 7059 | 200 |00:00:00.30 | 5480 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| SALES | 1 | 540K| 540K|00:00:00.19 | 5480 |
|* 3 | INDEX RANGE SCAN | SALES_CHANNEL_IX | 1 | 540K| 540K|00:00:00.08 | 1059 |
-------------------------------------------------------------------------------------------------------------------
Cost Based Optimizer가 사용되고 있다면 항상 고정화된 인덱스의 사용 유/무의 규칙은 존재하지 않는다.
경우에 따라 인덱스의 사용 유/무는 달라질 수 있으므로 실행 계획을 확인하는 습관이 필요하다.
실제 실행 계획을 살펴보면
HAVING 절을 이용하여 DEPTNO 칼럼의 조건식이 존재하지만 DEPTNO 칼럼의 인덱스를 사용하지 않았다.
인덱스의 사용 유/무를 결정하는 것은 일반적으로 WHERE 절에 의해서 결정되므로 일반 칼럼의 조건식은 WHERE을 이용한다.
-- 조건식이 없는 SQL
SELECT deptno, SUM(sal)
FROM emp
GROUP BY deptno
HAVING deptno IN (10,20) ;
-- where 조건식이 존재하는 SQL
SELECT deptno, SUM(sal)
FROM emp
WHERE deptno IN (10,20)
GROUP BY deptno ;
// 조건식이 없는 SQL의 실행 계획
-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 2 | | | |
|* 1 | FILTER | | 1 | | 2 |00:00:00.01 | 2 | | | |
| 2 | HASH GROUP BY | | 1 | 1 | 3 |00:00:00.01 | 2 | 1452K| 1452K| 11M (0)|
| 3 | TABLE ACCESS FULL| EMP | 1 | 14 | 14 |00:00:00.01 | 2 | | | |
-----------------------------------------------------------------------------------------------------------------
// where 조건식을 추가한 SQL의 실행 계획
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 3 |
| 1 | SORT GROUP BY NOSORT | | 1 | 2 | 2 |00:00:00.01 | 3 |
| 2 | INLIST ITERATOR | | 1 | | 8 |00:00:00.01 | 3 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 2 | 8 | 8 |00:00:00.01 | 3 |
|* 4 | INDEX RANGE SCAN | EMP_DEPTNO_IX | 2 | 8 | 8 |00:00:00.01 | 2 |
---------------------------------------------------------------------------------------------------------
모든 인덱스는 칼럼에 저장되어 있는 값을 기반으로 정렬되어 있다. 때문에 계산된 결과를 비교하거나 예제와 같이 함수를
적용한 칼럼은 인덱스를 사용하는 실행 계획을 생성하지 않는다.
해당 쿼리를 튜닝하기 위해 Function Based Index를 이용하여 계산된 값을 저장하도록 인덱스를 생성하거나
칼럼의 가공 또는 변형이 발생하지 않도록 조건 식을 수정한다.
-- 칼럼의 변경으로 FULL SCAN 쿼리
SELECT cust_id, cust_first_name, cust_last_name, cust_postal_code
FROM customers
WHERE SUBSTR(cust_last_name,1,3) = 'You' ;
-- 칼럼이 변형되지 않도록 조건 식 수정한 쿼리
SELECT cust_id, cust_first_name, cust_last_name, cust_postal_code
FROM customers
WHERE cust_last_name LIKE 'You%' ;
-- Function Based 인덱스 생성
CREATE INDEX custs_ix01 ON customers(SUBSTR(cust_last_name,1,3)) ;
-- 관련 함수 인덱스 힌트 사용 쿼리
SELECT /*+ INDEX (c cust_ix01) */
cust_id, cust_first_name, cust_last_name, cust_postal_code
FROM customers c
WHERE SUBSTR(cust_last_name,1,3) = 'You' ;
// 칼럼의 변형으로 FULL SCAN 쿼리 실행 계획
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 80 |00:00:00.01 | 1456 |
|* 1 | TABLE ACCESS FULL| CUSTOMERS | 1 | 555 | 80 |00:00:00.01 | 1456 |
-----------------------------------------------------------------------------------------
// 칼럼이 변형되지 않도록 조건 식 수정한 쿼리 실행 계획
-----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 80 |00:00:00.01 | 13 | 1 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS | 1 | 61 | 80 |00:00:00.01 | 13 | 1 |
|* 2 | INDEX RANGE SCAN | CUST_LAST_NAME_IDX | 1 | 61 | 80 |00:00:00.01 | 2 | 1 |
-----------------------------------------------------------------------------------------------------------------------------
// 함수 기반 인덱스 생성한 쿼리 실행 계획
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 80 |00:00:00.01 | 13 | 1 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS | 1 | 555 | 80 |00:00:00.01 | 13 | 1 |
|* 2 | INDEX RANGE SCAN | CUSTS_IX01 | 1 | 222 | 80 |00:00:00.01 | 2 | 1 |
---------------------------------------------------------------------------------------------------------------------
일반 인덱스 (B*Tree Index)는 NULL의 ROWID를 기록하고 있지 않으므로 조건 식의 IS NULL의 비교는 항상 인덱스를
사용할 수 없다.
NULL 을 갖는 행의 수가 많은 경우라면 FULL TABLE SCAN이 올바른 실행 계획이겠으나 소수의 행만이 NULL 을 가지고 있다면
INDEX SCAN이 보다 나은 실행 계획일 수 있다.
INDEX SCAEN을 위해 Function Based Index를 이용하여 NULL 의 ROWID를 인덱스에 저장하게 하거나 Bitmap Index를 이용한다.
-- IS NULL 조건의 FULL SCAN 쿼리
SELECT empno, ename, sal, comm, deptno
FROM emp
WHERE comm IS NULL ;
-- 함수 기반 인덱스 생성
CREATE INDEX emp_fix ON emp(NVL(comm,-1)) ;
-- 해당 인덱스를 사용한 쿼리
SELECT empno, ename, sal, comm, deptno
FROM emp
-- 컬럼 값이 NULL일 경우 치환하여 저장
WHERE NVL(comm,-1) = -1 ;
IS NULL 의 비교는 BTree Index를 이용하는 경우 항상 인덱스를 사용할 수 없다.
NULL 의 값이 대량으로 들어 있어서 인덱스를 사용하지 않아도 상관없는 경우를 제외하고 소량의 NULL을 검색해야 하는 경우가 있다면
함수 기반 인덱스를 활용한다.
*가급적 칼럼에 NULL 이 존재하지 않도록 DEFAULT 값을 지정하거나 임의의 다른 값으로 NULL을 대체하는 것이 더 좋은 방법이 된다.
// IS NULL 조건의 FULL SCAN 쿼리 실행 계획
------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 2 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 11 | 10 |00:00:00.01 | 2 |
------------------------------------------------------------------------------------
// 함수 기반 인덱스를 사용한 쿼리의 실행 계획
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 2 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 1 | 10 |00:00:00.01 | 2 |
|* 2 | INDEX RANGE SCAN | EMP_FIX | 1 | 1 | 10 |00:00:00.01 | 1 |
---------------------------------------------------------------------------------------------------------
IS NOT NULL 의 비교에 Table Full Scan 이 사용되었다. 하지만 테이블의 대부분의 행을 검색하는 경우이므로 Full Table
Scan 이 올바른 실행 계획임을 확인할 수 있다.
-- IS NOT NULL 풀스캔이 유리한 SQL
SELECT employee_id, last_name, commission_pct, salary
FROM employees
WHERE salary IS NOT NULL ;
IS NOT NULL 의 비교 시 검색 되는 데이터의 양에 따라 Full Table Scan 과 Index Scan 이 각각 사용된다. 힌트를
이용하여 인덱스의 사용을 강제화 시킨 후 작업량을 비교한다.
힌트를 이용하여 인덱스 사용을 강제화 시켰으며 실제 실행 계획을 확인하면 Index Full Scan을 이용하여 오히려
Full Table Scan 보다 작업량이 늘어난 것을 확인 할 수 있다
-- 힌트를 사용하여 인덱스 사용을 강제한 SQL
SELECT /*+ index(employees(salary)) */
employee_id, last_name, commission_pct, salary
FROM employees영
WHERE salary IS NOT NULL ;
인덱스에는 NULL 의 ROWID를 보관하지 않는다. 그렇다면 IS NOT NULL 은 인덱스의 모든 행을 검색해야 하는 상황이므로
Index Full Scan 은 어쩔 수 없는 선택이 된다. 때문에 칼럼의 대부분의 값이 NULL이고 값을 가지고 있는 경우가
소량이라면 인덱스를 활용하는 것이 좋을 수도 있다.
=> IS NULL 의 비교는 인덱스를 항상 사용하지 못한다. 하지만 IS NOT NULL 은 경우에 따라 인덱스를 사용할 수 있다. 단,
INDEX FULL SCAN이 사용됨을 확인하고 작업의 범위가 넓지 않은 경우에만 인덱스를 활용할 수 있도록 한다.
-- IS NOT NULL 인덱스 풀스캔이 유리한 SQL
SELECT empno, ename, sal, comm, deptno
FROM emp
WHERE comm IS NOT NULL ;
// IS NOT NULL 풀스캔이 유리한 SQL의 실행 계획
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 107 |00:00:00.01 | 3 |
|* 1 | TABLE ACCESS FULL| EMPLOYEES | 1 | 107 | 107 |00:00:00.01 | 3 |
-----------------------------------------------------------------------------------------
// 힌트를 사용하여 인덱스 사용을 강제한 SQL의 실행 계획
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 107 |00:00:00.01 | 18 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES | 1 | 107 | 107 |00:00:00.01 | 18 |
|* 2 | INDEX FULL SCAN | EMPL_SAL_IX | 1 | 107 | 107 |00:00:00.01 | 1 |
-------------------------------------------------------------------------------------------------------------
// IS NOT NULL 인덱스 풀스캔이 유리한 SQL의 실행 계획
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 4 |00:00:00.01 | 2 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 4 | 4 |00:00:00.01 | 2 |
|* 2 | INDEX FULL SCAN | EMP_COMM_IX | 1 | 4 | 4 |00:00:00.01 | 1 |
-------------------------------------------------------------------------------------------------------------
-- 와일드 카드가 뒤에 오는 경우 SQL
SELECT cust_id, cust_first_name, cust_last_name, cust_postal_code
FROM customers
WHERE cust_last_name LIKE 'You%' ;
LIKE 비교 시 %를 앞에 사용했으므로 Table Full Scan 이 사용되었다. 대량의 데이터를 검색하는 경우라면 상관없겠으나
한 개의 행을 검색하는데 Full Table Scan 은 비효율적인 엑세스 방법이다.
-- 와일드 카드가 앞에 오는 경우 SQL
SELECT cust_id, cust_first_name, cust_last_name, cust_postal_code
FROM customers
WHERE cust_last_name LIKE '%itt' ;
힌트를 이용하여 인덱스 사용을 강제화 시켰으며 Index Full Scan 이 이용되었다. 역시 한 개의 행을 찾기 위해 불필요한
Index Full Scan 은 효율적인 엑세스 방법은 아니다
-- 와일드 카드가 앞에 오는 경우 SQL (인덱스 강제 힌트 사용)
SELECT /*+ index(c(cust_last_name)) */
cust_id, cust_first_name, cust_last_name, cust_postal_code
FROM customers c
WHERE cust_last_name LIKE '%itt' ;
이를 해결하기 위한 방법으로 칼럼을 분리하여 LIKE 비교 시 앞에 %,_ 를 사용하지 않거나 Function Based Index를 이용한다.
-- 조회 조건의 함수 인덱스를 생성
CREATE INDEX custs_fix ON customers(REVERSE(cust_last_name)) ;
-- 조회 조건의 함수 인덱스 사용한 SQL
SELECT cust_id, cust_first_name, cust_last_name, cust_postal_code
FROM customers
WHERE REVERSE(cust_last_name) LIKE 'tti%' ;
=> LIKE 비교 시 Full Table Scan을 해도 상관없는 경우를 제외한 앞에 %,_ 를 사용하지 않는다. 만약 사용한다면 인덱스를
사용할 수 있도록 Function Based Index를 구성하거나 칼럼을 분리하여 저장한다.
// 와일드 카드가 뒤에 오는 경우 SQL 실행 계획
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 80 |00:00:00.01 | 13 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS | 1 | 61 | 80 |00:00:00.01 | 13 |
|* 2 | INDEX RANGE SCAN | CUST_LAST_NAME_IDX | 1 | 61 | 80 |00:00:00.01 | 2 |
--------------------------------------------------------------------------------------------------------------------
// 와일드 카드가 앞에 오는 경우 SQL 실행 계획
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 1456 |
|* 1 | TABLE ACCESS FULL| CUSTOMERS | 1 | 1 | 1 |00:00:00.01 | 1456 |
-----------------------------------------------------------------------------------------
// 와일드 카드가 앞에 오는 경우 SQL (인덱스 강제 힌트 사용) 실행 계획
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 143 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS | 1 | 2775 | 1 |00:00:00.01 | 143 |
|* 2 | INDEX FULL SCAN | CUST_LAST_NAME_IDX | 1 | 2775 | 1 |00:00:00.01 | 142 |
--------------------------------------------------------------------------------------------------------------------
// 조회 조건의 함수 인덱스 사용한 SQL 실행 계획
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 | 1 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS | 1 | 2775 | 1 |00:00:00.01 | 3 | 1 |
|* 2 | INDEX RANGE SCAN | CUSTS_FIX | 1 | 499 | 1 |00:00:00.01 | 2 | 1 |
--------------------------------------------------------------------------------------------------------------------
NOT IN 과 같은 부정형 비교 사용 시 인덱스 사용은 불가능 한 경우가 많다.
부정형 비교는 특정 값을 제외한 나머지 모든 값을 비교해야 하는 상황이므로 인덱스의 검색 범위가 넓어질 수 있다는 단점이 있기 때문에
Rule Based Optimizer에서는 항상 인덱스를 사용하지 않았다.
하지만 Cost Based Optimizer 가 사용된다면 검색 되는 데이터의 양을 비교하여 인덱스를 사용하는 실행계획이 만들어지는 경우도 존재한다.
단, 부정형 비교는 특정 값을 제외한 나머지 모든 값이므로 Index Full Scan 이 사용되는 것을 확인할 수 있다.
-- 부정형 비교 조건 SQL
SELECT employee_id, last_name, salary, department_id
FROM employees
WHERE department_id NOT IN (10,20) ;
-- 부정형 비교 조건 SQL (인덱스 사용 강제 힌트)
SELECT /*+ index(employees(department_id)) */
employee_id, last_name, salary, department_id
FROM employees
WHERE department_id NOT IN (10,20) ;
부정형 비교의 사용은 가급적 자제하는 것이 좋다. Index Full Scan 의 범위가 극히 적을 경우가 아니라면 부정형 비교가
아닌 다른 방법으로 동일한 결과를 검색할 수 있도록 문장을 작성한다.
=> 부정형 비교 연산자의 사용을 자제하며 필요시 실행계획을 확인하여 인덱스를 활용할 수 있도록 적절한 힌트를 지정한다.
-- 부정형 비교 조건과 동일한 결과의 조건을 사용한 SQL
SELECT /*+ index(employees(department_id)) */
employee_id, last_name, salary, department_id
FROM employees
WHERE department_id IN (30,40,50,60,70,80,90,100,110) ;
-- 부정형 비교 조건과 동일한 결과의 조건을 사용한 SQL
SELECT /*+ index(employees(department_id)) */
employee_id, last_name, salary, department_id
FROM employees
WHERE department_id > 20 ;
employee_id 컬럼에 인덱스가 분명 존재하지만 실행 계획 상에서는 FULL SCAN을 수행한다.
employee_id의 데이터타입은 문자열이지만 where절 비교 대상이 숫자이므로 암시적인 형변환을
처리하며 FULL SCAN을 수행하게 된다.
따라서 인덱스 스캔을 사용하기 위해서는 데이터 타입을 맞춰 사용해야 한다.
SELECT employee_id, last_name, hire_date, salary, department_id
FROM employee
WHERE employee_id = 100 ;
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 4 | 2 |
|* 1 | TABLE ACCESS FULL| EMPLOYEE | 1 | 1 | 1 |00:00:00.01 | 4 | 2 |
-------------------------------------------------------------------------------------------------