인덱스 열 순서를 선택할 때 가장 중요한 관심사는 다음과 같습니다.
쿼리에서이 열에 대해 (동일) 조건자가 있습니까?
where 절에 열이 나타나지 않으면 인덱싱 할 가치가 없습니다 (1)
자, 각 열에 대한 테이블과 쿼리가 있습니다. 때때로 하나 이상.
색인을 생성 할 대상을 어떻게 결정합니까?
예를 봅시다. 다음은 세 개의 열이있는 테이블입니다. 하나는 10 개의 값을 보유하고 다른 하나는 마지막 10,000을 보유합니다.
create table t(
few_vals varchar2(10),
many_vals varchar2(10),
lots_vals varchar2(10)
);
insert into t
with rws as (
select lpad(mod(rownum, 10), 10, '0'),
lpad(mod(rownum, 1000), 10, '0'),
lpad(rownum, 10, '0')
from dual connect by level <= 10000
)
select * from rws;
commit;
select count(distinct few_vals),
count(distinct many_vals) ,
count(distinct lots_vals)
from t;
COUNT(DISTINCTFEW_VALS) COUNT(DISTINCTMANY_VALS) COUNT(DISTINCTLOTS_VALS)
10 1,000 10,000
이들은 0으로 채워진 숫자입니다. 이렇게하면 나중에 압축에 대한 정보를 얻을 수 있습니다.
따라서 세 가지 일반적인 쿼리가 있습니다.
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where few_vals = '0000000001';
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where lots_vals = '0000000001';
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where lots_vals = '0000000001'
and few_vals = '0000000001';
당신은 무엇을 색인합니까?
few_vals에 대한 인덱스는 전체 테이블 스캔보다 약간 우수합니다.
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where few_vals = '0000000001';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 61 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 61 |
| 2 | VIEW | VW_DAG_0 | 1 | 1000 | 1000 |00:00:00.01 | 61 |
| 3 | HASH GROUP BY | | 1 | 1000 | 1000 |00:00:00.01 | 61 |
| 4 | TABLE ACCESS FULL| T | 1 | 1000 | 1000 |00:00:00.01 | 61 |
-------------------------------------------------------------------------------------------
select /*+ index (t (few_vals)) */
count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where few_vals = '0000000001';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 58 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 58 |
| 2 | VIEW | VW_DAG_0 | 1 | 1000 | 1000 |00:00:00.01 | 58 |
| 3 | HASH GROUP BY | | 1 | 1000 | 1000 |00:00:00.01 | 58 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| T | 1 | 1000 | 1000 |00:00:00.01 | 58 |
| 5 | INDEX RANGE SCAN | FEW | 1 | 1000 | 1000 |00:00:00.01 | 5 |
-------------------------------------------------------------------------------------------------------------
따라서 자체적으로 인덱싱 할 가치가 없을 것입니다. lots_vals의 쿼리는 몇 개의 행을 반환합니다 (이 경우 1 만). 따라서 이것은 인덱싱 할 가치가 있습니다.
그러나 두 열에 대한 쿼리는 어떻습니까?
색인을 작성해야합니까?
( few_vals, lots_vals )
또는
( lots_vals, few_vals )
트릭 질문!
대답은 없습니다.
물론 few_vals는 긴 문자열입니다. 따라서 압축을 잘 풀 수 있습니다. 그리고 lots_vals에 술어 만있는 (few_vals, lots_vals)를 사용하여 쿼리에 대한 인덱스 스킵 스캔을 얻을 수 있습니다. 그러나 전체 스캔보다 현저하게 성능이 우수하지만 여기에 없습니다.
create index few_lots on t(few_vals, lots_vals);
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where lots_vals = '0000000001';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 61 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 61 |
| 2 | VIEW | VW_DAG_0 | 1 | 1 | 1 |00:00:00.01 | 61 |
| 3 | HASH GROUP BY | | 1 | 1 | 1 |00:00:00.01 | 61 |
| 4 | TABLE ACCESS FULL| T | 1 | 1 | 1 |00:00:00.01 | 61 |
-------------------------------------------------------------------------------------------
select /*+ index_ss (t few_lots) */count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where lots_vals = '0000000001';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 13 | 11 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 13 | 11 |
| 2 | VIEW | VW_DAG_0 | 1 | 1 | 1 |00:00:00.01 | 13 | 11 |
| 3 | HASH GROUP BY | | 1 | 1 | 1 |00:00:00.01 | 13 | 11 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| T | 1 | 1 | 1 |00:00:00.01 | 13 | 11 |
| 5 | INDEX SKIP SCAN | FEW_LOTS | 1 | 40 | 1 |00:00:00.01 | 12 | 11 |
----------------------------------------------------------------------------------------------------------------------
도박을 좋아합니까? (2)
따라서 여전히 주요 열로 lots_vals가있는 인덱스가 필요합니다. 그리고이 경우 적어도 복합 지수 (몇, 로트)는 하나의 작업과 동일한 양의 작업을 수행합니다 (많은)
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where lots_vals = '0000000001'
and few_vals = '0000000001';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 3 |
| 2 | VIEW | VW_DAG_0 | 1 | 1 | 1 |00:00:00.01 | 3 |
| 3 | HASH GROUP BY | | 1 | 1 | 1 |00:00:00.01 | 3 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| T | 1 | 1 | 1 |00:00:00.01 | 3 |
| 5 | INDEX RANGE SCAN | FEW_LOTS | 1 | 1 | 1 |00:00:00.01 | 2 |
-------------------------------------------------------------------------------------------------------------
create index lots on t(lots_vals);
select /*+ index (t (lots_vals)) */count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where lots_vals = '0000000001'
and few_vals = '0000000001';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 | 1 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
| 2 | VIEW | VW_DAG_0 | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
| 3 | HASH GROUP BY | | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| T | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
| 5 | INDEX RANGE SCAN | LOTS | 1 | 1 | 1 |00:00:00.01 | 2 | 1 |
----------------------------------------------------------------------------------------------------------------------
복합 인덱스가 1-2 IO를 절약하는 경우가 있습니다. 그러나이 절약을 위해 두 가지 색인을 가질 가치가 있습니까?
복합 지수에는 또 다른 문제가 있습니다. LOTS_VALS를 포함하여 세 개의 인덱스에 대한 클러스터링 요소를 비교하십시오.
create index lots on t(lots_vals);
create index lots_few on t(lots_vals, few_vals);
create index few_lots on t(few_vals, lots_vals);
select index_name, leaf_blocks, distinct_keys, clustering_factor
from user_indexes
where table_name = 'T';
INDEX_NAME LEAF_BLOCKS DISTINCT_KEYS CLUSTERING_FACTOR
FEW_LOTS 47 10,000 530
LOTS_FEW 47 10,000 53
LOTS 31 10,000 53
FEW 31 10 530
few_lots의 군집 계수는 lot 및 lots_few보다 10 배 높습니다 ! 그리고 이것은 완벽한 클러스터링을 갖춘 데모 테이블에 있습니다. 실제 데이터베이스에서는 효과가 더 나빠질 수 있습니다.
그래서 뭐가 그렇게 나쁜가요?
클러스터링 팩터는 인덱스의 "매력적인"정도를 결정하는 주요 동인 중 하나입니다. 높을수록 최적화 프로그램이 선택할 가능성이 줄어 듭니다. 특히 lots_vals가 실제로 고유하지는 않지만 일반적으로 값당 행이 거의 없습니다. 운이 좋지 않으면 옵티마이 저가 전체 스캔이 더 저렴하다고 생각하게 할 수 있습니다 ...
좋습니다. 따라서 few_vals 및 lots_vals가있는 복합 인덱스에는 대소 문자 만 이점이 있습니다.
few_vals 및 many_vals를 필터링하는 쿼리는 어떻습니까?
단일 열 인덱스는 작은 이점 만 제공합니다. 그러나 결합하면 값이 거의 반환되지 않습니다. 따라서 복합 인덱스는 좋은 생각입니다. 그러나 어느 방향으로?
몇 개를 먼저 배치하지 않으면 선행 열을 압축하면 크기가 작아집니다.
create index few_many on t(many_vals, few_vals);
create index many_few on t(few_vals, many_vals);
select index_name, leaf_blocks, distinct_keys, clustering_factor
from user_indexes
where index_name in ('FEW_MANY', 'MANY_FEW');
INDEX_NAME LEAF_BLOCKS DISTINCT_KEYS CLUSTERING_FACTOR
FEW_MANY 47 1,000 10,000
MANY_FEW 47 1,000 10,000
alter index few_many rebuild compress 1;
alter index many_few rebuild compress 1;
select index_name, leaf_blocks, distinct_keys, clustering_factor
from user_indexes
where index_name in ('FEW_MANY', 'MANY_FEW');
INDEX_NAME LEAF_BLOCKS DISTINCT_KEYS CLUSTERING_FACTOR
MANY_FEW 31 1,000 10,000
FEW_MANY 34 1,000 10,000
선행 열에서 다른 값이 적을수록 더 잘 압축됩니다. 따라서이 인덱스를 읽는 작업이 거의 없습니다. 그러나 약간만. 그리고 둘 다 이미 원본보다 작은 덩어리입니다 (25 % 크기 감소).
더 나아가 전체 인덱스를 압축 할 수 있습니다!
alter index few_many rebuild compress 2;
alter index many_few rebuild compress 2;
select index_name, leaf_blocks, distinct_keys, clustering_factor
from user_indexes
where index_name in ('FEW_MANY', 'MANY_FEW');
INDEX_NAME LEAF_BLOCKS DISTINCT_KEYS CLUSTERING_FACTOR
FEW_MANY 20 1,000 10,000
MANY_FEW 20 1,000 10,000
이제 두 인덱스가 같은 크기로 돌아 왔습니다. 이것은 소수와 많은 관계가 있다는 사실을 이용합니다. 다시 한 번 현실 세계에서 이런 종류의 혜택을 볼 수 없을 것입니다.
지금까지는 평등 검사에 대해서만 이야기했습니다. 복합 인덱스를 사용하면 열 중 하나에 대해 불평등이 발생합니다. 예 : "지난 N 일 동안 고객에 대한 주문 / 배송 / 인보이스 받기"와 같은 쿼리
이러한 종류의 쿼리가있는 경우 인덱스의 첫 번째 열에 대해 동등성을 원합니다.
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where few_vals < '0000000002'
and many_vals = '0000000001';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 12 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 12 |
| 2 | VIEW | VW_DAG_0 | 1 | 10 | 10 |00:00:00.01 | 12 |
| 3 | HASH GROUP BY | | 1 | 10 | 10 |00:00:00.01 | 12 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| T | 1 | 10 | 10 |00:00:00.01 | 12 |
| 5 | INDEX RANGE SCAN | FEW_MANY | 1 | 10 | 10 |00:00:00.01 | 2 |
-------------------------------------------------------------------------------------------------------------
select count (distinct few_vals || ':' || many_vals || ':' || lots_vals )
from t
where few_vals = '0000000001'
and many_vals < '0000000002';
select *
from table(dbms_xplan.display_cursor(null, null, 'IOSTATS LAST -PREDICATE'));
----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 12 | 1 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 12 | 1 |
| 2 | VIEW | VW_DAG_0 | 1 | 2 | 10 |00:00:00.01 | 12 | 1 |
| 3 | HASH GROUP BY | | 1 | 2 | 10 |00:00:00.01 | 12 | 1 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| T | 1 | 2 | 10 |00:00:00.01 | 12 | 1 |
| 5 | INDEX RANGE SCAN | MANY_FEW | 1 | 1 | 10 |00:00:00.01 | 2 | 1 |
----------------------------------------------------------------------------------------------------------------------
반대 인덱스를 사용하고 있습니다.
TL; DR
- 동등 조건을 가진 열이 색인에서 먼저 가야합니다.
- 쿼리에 동일한 열을 가진 여러 열이있는 경우 가장 적은 값을 가진 열을 먼저 배치하면 압축 이점이 가장 좋습니다
- 인덱스 스킵 스캔이 가능하지만, 이것은 가까운 장래에 실행 가능한 옵션으로 유지 될 것이라는 확신이 필요합니다
- 거의 고유 한 열을 포함한 복합 인덱스는 최소한의 이점을 제공합니다. 1-2 IO를 저장해야합니다
1 : 쿼리의 모든 열이 인덱스에 있음을 의미하는 경우 인덱스에 열을 포함시키는 것이 좋습니다. 이렇게하면 인덱스 만 검색 할 수 있으므로 테이블에 액세스 할 필요가 없습니다.
2 : 진단 및 조정에 대한 라이센스가있는 경우 SQL 계획 관리를 사용하여 계획을 건너 뛰도록 강제로 계획 할 수 있습니다.
ADDEDNDA
추신-당신이 인용 한 문서는 9i에서 온 것입니다. 그 말은 오래되었습니다. 나는 더 최근의 것을 고수했다