함수 기반 인덱스
- 오라클 8.1.5 버전에서 추가
- 9iR2이하 버전에서는 Enterprise Edition에서만 지원
- 계산된 컬럼에 인덱스를 생성하여 쿼리에서 이 인덱스를 사용하는 것
- 함수 기반 인덱스는 구현하기 쉬울뿐만 아니라 효과를 즉시 알 수 있다.
- 기존 애플라케이션의 로직이나 질의를 전혀 바꾸지 않고 수행 속도를 개선할 수 있다.
- 9iR1 이하 버전에서는 아래의 권한 필요.
- QUERY REWRITE 시스템 권한(사용자의 스키마에 생성시)
- GLOBAL QUERY REWRITE 시스템 권한(다른 사용자의 스키마에 생성시)
- 옵티마이저가 함수 기반 인텍스를 사용하기 위해서 QUERY_REWRITE_ENABLED=TRUE와
QUERY REWRITEJNTEGRITY=TRUSTED을 세션 또는 시스템 레벨에서 정의- QUERY_REWRITE_ENABLED은 옵티마이저가 함수 기반 인텍스를 이용하여 쿼리 를 변형 (transformation)하도록 허용
- QUERY REWRITE_INTEGRITY는 옵티마이저가 프로그래머가 deterministic이라고 표기된 코드가 실제로 그렇다는 것을 믿으라는 의미
- DETERMINISTIC : 동일한 입력값에 대해서는 항상 통일한 결과값을 돌려준다는 것을 의미.
- 모든 버전에서 적용되는 사항
- 비용 기반 옵티마이저 (CBO)를 사용한다.
함수 기반 인텍스의 가상 컬럼 (함수가 적용된 컬럼)은 CBO에게만 인식.
규칙 기반 옵티마이저 (RBO)에 의해서는 전혀 시용되지 않는다. RBO는 함수 기반 인텍스를 구성하는 컬럼 중 아무런 함수도 적용되지 않는 선두 컬럼을 이용할 수 있다 - VARCHAR2 또는 RAW 타입을 반환하는 사용자 정의 함수로부터 리턴값을 제어하기 위해 SUBSTR을 사용한다
create table emp
as
select *
from scott.emp
where 1=0;
insert into emp
(empno,ename,job ,mgr ,hiredate ,sal ,comm,deptno)
select rownum empno,
initcap(substr(object_name,1,10)) ename,
substr(object_type, 1 ,9) JOB,
rownum MGR ,
created hiredate,
rownum SAL,
rownum COMM,
(mod(rownum , 4)+1)*10 DEPTNO
from all_objects
where rownum < 10000;
create index emp_upper_idx on emp(upper(ename));
begin
dbms_stats.gather_table_stats(user, 'EMP' , cascade=>true);
end;
/
set autot on
select *
from emp
where upper(ename) = 'KING';
Execution Plan
----------------------------------------------------------
Plan hash value: 1576737566
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 110 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 2 | 110 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_UPPER_IDX | 2 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(UPPER("ENAME")='KING')
- 사용자 정의 함수로 인덱스를 생성하기 위해서는 deterministic 키워드를 사용.
- 문자를 리턴하는 사용자 정의 함수에서 SUBSTR을 정의하지 않으면, VARCHAR2(4000) 유형으로 결과를 리턴.
인덱스 엔트리가 블록의 3/4이내로 정의되어야 하기 때문에 그 이상의 경우 오류 발생 (4k 블럭의 경우 오류 발생)
-- 패키지 전역변수 선언
create or replace package stats
as
cnt number default 0;
end;
/
-- 사용자 정의 함수 선언
create or replace
function my_soundex(p_string in varchar2) return varchar2
deterministic
as
l_return_string varchar2(6) default substr(p_string,1,1);
l_char varchar2(1);
l_last_digit number default 0;
type vcArray is table of varchar2(10) index by binary_integer;
l_code_table vcArray;
begin
stats.cnt :=stats.cnt+1;
l_code_table(1) := 'BPFV';
l_code_table(2) := 'CSKGJQXZ';
l_code_table(3) := 'DT';
l_code_table(4) := 'L';
l_code_table(5) := 'MN';
l_code_table(6) := 'R';
for i in 1..length(p_string)
loop
exit when (length(l_return_string) = 6);
l_char := upper(substr(p_string,i,1));
for j in 1 .. l_code_table.count
loop
if(instr(l_code_table(j),l_char) > 0 and j <> l_last_digit)
then
l_return_string :=l_return_string || to_char(j,'fm9');
l_last_digit := j;
end if;
end loop;
end loop;
return lpad(l_return_string,6,'0');
end;
/
-- 8K의 경우
create index emp_soundex_idx on
emp (my_soundex(ename), my_soundex(job));
emp (my_soundex(ename), my_soundex(job))
*
ERROR at line 2:
ORA-01450: maximum key length (6398) exceeded
-- FBI 인덱스가 없는 경우
create table emp_no_index
as
select *
from scott.emp
where 1=0;
insert into emp_no_index
(empno,ename,job ,mgr ,hiredate ,sal ,comm,deptno)
select rownum empno,
initcap(substr(object_name,1,10)) ename,
substr(object_type, 1 ,9) JOB,
rownum MGR ,
created hiredate,
rownum SAL,
rownum COMM,
(mod(rownum , 4)+1)*10 DEPTNO
from all_objects
where rownum < 10000;
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.078 0.070 0 11 0 0
Execute 1 0.218 0.225 0 3399 983 9999
Fetch 0 0.000 0.000 0 0 0 0
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 2 0.296 0.295 0 3410 983 9999
-- FBI 인덱스가 있는 경우
create table emp_index
as
select *
from scott.emp
where 1=0;
create index emp_index_soundex_idx on
emp_index(substr(my_soundex(ename),1,6) );
insert into emp_index
(empno,ename,job ,mgr ,hiredate ,sal ,comm,deptno)
select rownum empno,
initcap(substr(object_name,1,10)) ename,
substr(object_type, 1 ,9) JOB,
rownum MGR ,
created hiredate,
rownum SAL,
rownum COMM,
(mod(rownum , 4)+1)*10 DEPTNO
from all_objects
where rownum < 10000;
Call Count CPU Time Elapsed Time Disk Query Current Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse 1 0.062 0.063 0 16 0 0
Execute 1 0.562 0.565 0 3779 9564 9999
Fetch 0 0.000 0.000 0 0 0 0
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total 2 0.624 0.628 0 3795 9564 9999
-- 인덱스가 없는 경우
exec stats.cnt := 0
set autotrace on explain
variable cpu number
exec :cpu := dbms_utility.get_cpu_time
select ename, hiredate
from emp
where my_soundex(ename) = my_soundex('Kings')
;
ENAME HIREDATE
---------- ------------
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
4 rows selected.
Elapsed: 00:00:00.30
Execution Plan
----------------------------------------------------------
Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 1900 | 25 (12)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMP | 100 | 1900 | 25 (12)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("MY_SOUNDEX"("ENAME")="MY_SOUNDEX"('Kings'))
set sutotrace off
begin
dbms_output.put_line
('cpu_time = ' || round((dbms_utility.get_cpu_time-:cpu)/100,2) );
dbms_output.put_line('function was called: ' || stats.cnt);
end;
/
cpu_time = .29
function was called: 9788
-- 인덱스가 있는 경우
create index emp_soundex_idx on
emp (substr(my_soundex(ename), 1, 6))
/
exec stats.cnt := 0
set autotrace on explain
variable cpu number
exec :cpu := dbms_utility.get_cpu_time
select ename, hiredate
from emp
where my_soundex(ename) = my_soundex('Kings')
;
ENAME HIREDATE
---------- ------------
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
4 rows selected.
Elapsed: 00:00:00.02
Execution Plan
----------------------------------------------------------
Plan hash value: 2259214921
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 2700 | 12 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| EMP | 100 | 2700 | 12 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_SOUNDEX_IDX | 40 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("MY_SOUNDEX"("ENAME")="MY_SOUNDEX"('Kings'))
2 - access(SUBSTR("KJWON"."MY_SOUNDEX"("ENAME"),1,6)=SUBSTR("MY_SOUNDEX"('Kings'),1,
6))
set sutotrace off
begin
dbms_output.put_line
('cpu_time = ' || round((dbms_utility.get_cpu_time-:cpu)/100,2) );
dbms_output.put_line('function was called: ' || stats.cnt);
end;
/
cpu_time = .03
function was called: 4
-- 11gR1 이상에서 지원하는 가상 컬럼 이용
drop index emp_soundex_idx;
alter table emp
add ename_soundex as (substr(my_soundex(ename), 1, 6))
/
create index emp_soundex_idx
on emp(ename_soundex);
exec stats.cnt := 0
set autotrace on explain
variable cpu number
exec :cpu := dbms_utility.get_cpu_time
select ename, hiredate
from emp
where ename_soundex = my_soundex('Kings')
;
ENAME HIREDATE
---------- ------------
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
Ku$_Chunk_ 03-NOV-11
4 rows selected.
Elapsed: 00:00:00.08
Execution Plan
----------------------------------------------------------
Plan hash value: 2259214921
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 2700 | 12 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 100 | 2700 | 12 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_SOUNDEX_IDX | 40 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ENAME_SOUNDEX"="MY_SOUNDEX"('Kings'))
set sutotrace off
begin
dbms_output.put_line
('cpu_time = ' || round((dbms_utility.get_cpu_time-:cpu)/100,2) );
dbms_output.put_line('function was called: ' || stats.cnt);
end;
/
cpu_time = .05
function was called: 1
몇 개 로우에만 인덱스 하기.
- 오라클에서의 B*Tree는인덱스 키 전체가 NULL 이면 생성되지 않는다.
create index idx_t
on t( case col when 'N' then 'N' end);
선택적 유일성 구현
- 일부 상태의 데이터들에 대해서만 유일성을 보장 해야 하는 경우 NULL인 엔트리는 B*Tree에서는 만들어지지 않는 것을 이용
create unique index idx_t
on t( case when status = 'ACTIVE' then name end);
ORA-01743 주의하기
- to_date에 대해서는 어떤 경우에는 함수 기반 인덱스를 만들 수 없다.
create table t (year varchar2(4));
create index t_idx on t(to_date(year, 'YYYY'));
create index t_idx on t(to_date(year, 'YYYY'))
*
ERROR at line 1:
ORA-01743: only pure functions can be indexed
create index t_idx on t(to_date('01'||year, 'MMYYYY'));
Index created.
-- YYYY 형식이 문제
-- 해당 월에 따라 데이터가 바뀔 수 있다.
select to_char(to_date('2005', 'YYYY'), 'YYYYMMDD HH24:MI:SS') from dual;
TO_CHAR(TO_DATE('
-----------------
20051101 00:00:00
1 row selected.