Khi chọn thứ tự cột chỉ mục, mối quan tâm ghi đè là:
Có các vị từ (bình đẳng) đối với cột này trong các truy vấn của tôi không?
Nếu một cột không bao giờ xuất hiện trong mệnh đề where, thì nó không có giá trị lập chỉ mục (1)
OK, vậy là bạn đã có một bảng và truy vấn đối với từng cột. Đôi khi nhiều hơn một.
Làm thế nào để bạn quyết định những gì để lập chỉ mục?
Hãy xem một ví dụ. Đây là một bảng có ba cột. Một giữ 10 giá trị, 1.000 khác, 10.000 cuối cùng:
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
Đây là những con số được đệm bằng số không. Điều này sẽ giúp làm cho điểm về nén sau này.
Vì vậy, bạn đã có ba truy vấn phổ biến:
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';
Bạn lập chỉ mục gì?
Một chỉ mục trên vài số chỉ tốt hơn một chút so với quét toàn bộ bảng:
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 |
-------------------------------------------------------------------------------------------------------------
Vì vậy, nó không có khả năng tự lập chỉ mục. Các truy vấn trên rất nhiều trả về vài hàng (chỉ 1 trong trường hợp này). Vì vậy, đây chắc chắn là chỉ số giá trị.
Nhưng những gì về các truy vấn đối với cả hai cột?
Bạn nên lập chỉ mục:
( few_vals, lots_vals )
HOẶC LÀ
( lots_vals, few_vals )
Câu hỏi mẹo!
Câu trả lời là không.
Chắc chắn vài_ số là một chuỗi dài. Vì vậy, bạn có thể có được nén tốt từ nó. Và bạn (có thể) có được một lần quét bỏ qua chỉ mục cho các truy vấn bằng cách sử dụng (few_ số, lot_ số) chỉ có các vị từ trên rất nhiều. Nhưng tôi không ở đây, mặc dù nó hoạt động tốt hơn rõ rệt so với quét toàn bộ:
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 |
----------------------------------------------------------------------------------------------------------------------
Bạn có thích cờ bạc không? (2)
Vì vậy, bạn vẫn cần một chỉ mục với rất nhiều cột làm cột hàng đầu. Và ít nhất trong trường hợp này, chỉ số ghép (vài, rất nhiều) thực hiện cùng một lượng công việc như một chỉ trên (rất nhiều)
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 |
----------------------------------------------------------------------------------------------------------------------
Sẽ có trường hợp chỉ số ghép giúp bạn tiết kiệm 1-2 IOs. Nhưng nó có đáng để có hai chỉ số cho việc tiết kiệm này không?
Và có một vấn đề khác với chỉ số tổng hợp. So sánh hệ số phân cụm cho ba chỉ mục bao gồm 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
Lưu ý rằng hệ số phân cụm cho few_lots cao hơn gấp 10 lần so với lot và lot_few! Và đây là trong một bảng demo với phân cụm hoàn hảo để bắt đầu. Trong cơ sở dữ liệu thế giới thực, hiệu quả có thể tồi tệ hơn.
Vì vậy, những gì xấu về điều đó?
Yếu tố phân cụm là một trong những yếu tố chính quyết định mức độ "hấp dẫn" của một chỉ mục. Nó càng cao, càng ít khả năng tối ưu hóa để chọn nó. Đặc biệt nếu lot_ số không thực sự là duy nhất, nhưng thông thường vẫn có vài hàng cho mỗi giá trị. Nếu bạn không may mắn, điều này có thể đủ để làm cho trình tối ưu hóa nghĩ rằng quét toàn bộ sẽ rẻ hơn ...
OK, do đó, các chỉ mục tổng hợp có vài số và rất nhiều chỉ có lợi ích trường hợp cạnh.
Điều gì về các truy vấn lọc vài số và nhiều số?
Chỉ mục cột đơn chỉ cung cấp lợi ích nhỏ. Nhưng kết hợp họ trả lại một vài giá trị. Vì vậy, một chỉ số tổng hợp là một ý tưởng tốt. Nhưng đường nào vòng?
Nếu bạn đặt vài cái trước, nén cột hàng đầu sẽ làm cho nó nhỏ hơn
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
Với ít giá trị khác nhau trong cột hàng đầu sẽ nén tốt hơn. Vì vậy, có rất ít công việc để đọc chỉ số này. Nhưng chỉ một chút thôi. Và cả hai đã là một đoạn tốt nhỏ hơn so với ban đầu (giảm 25% kích thước).
Và bạn có thể đi xa hơn và nén toàn bộ chỉ số!
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
Bây giờ cả hai chỉ mục trở lại cùng kích thước. Lưu ý điều này lợi dụng thực tế là có một mối quan hệ giữa vài và nhiều. Một lần nữa, không chắc bạn sẽ thấy loại lợi ích này trong thế giới thực.
Cho đến nay chúng ta chỉ nói về kiểm tra bình đẳng. Thông thường với các chỉ mục tổng hợp, bạn sẽ có bất đẳng thức so với một trong các cột. ví dụ: các truy vấn như "nhận đơn đặt hàng / giao hàng / hóa đơn cho khách hàng trong N ngày qua".
Nếu bạn có các loại truy vấn này, bạn muốn có sự bằng nhau so với cột đầu tiên của chỉ mục:
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 |
----------------------------------------------------------------------------------------------------------------------
Lưu ý rằng họ đang sử dụng chỉ mục ngược lại.
TL; DR
- Các cột có điều kiện bình đẳng nên đi đầu tiên trong chỉ mục.
- Nếu bạn có nhiều cột có giá trị bằng nhau trong truy vấn của mình, đặt cột có ít giá trị khác nhau trước sẽ mang lại lợi thế nén tốt nhất
- Mặc dù quét bỏ qua chỉ mục là có thể, bạn cần phải tự tin rằng đây sẽ vẫn là một lựa chọn khả thi cho tương lai gần
- Các chỉ mục tổng hợp bao gồm các cột gần như duy nhất mang lại lợi ích tối thiểu. Hãy chắc chắn rằng bạn thực sự cần phải lưu 1-2 IO
1: Trong một số trường hợp, có thể có giá trị bao gồm một cột trong một chỉ mục nếu điều này có nghĩa là tất cả các cột trong truy vấn của bạn nằm trong chỉ mục. Điều này cho phép chỉ mục quét, vì vậy bạn không cần truy cập vào bảng.
2: Nếu bạn được cấp phép cho Chẩn đoán và Điều chỉnh, bạn có thể buộc kế hoạch quét bỏ qua với Quản lý Kế hoạch SQL
THÊM
PS - tài liệu bạn đã trích dẫn có từ 9i. Đó là già thực sự. Tôi muốn gắn bó với một cái gì đó gần đây