เมื่อเลือกลำดับคอลัมน์ดัชนีข้อกังวลที่สำคัญคือ:
จะมี (ความเสมอภาค) เปรียบเทียบกับคอลัมน์นี้ในข้อความค้นหาของฉันหรือไม่?
หากคอลัมน์ไม่เคยปรากฏในส่วนคำสั่งที่ไหนก็ไม่คุ้มค่าการจัดทำดัชนี (1)
ตกลงดังนั้นคุณจะมีตารางและคิวรีสำหรับแต่ละคอลัมน์ บางครั้งมากกว่าหนึ่ง
คุณตัดสินใจเลือกสิ่งที่จะจัดทำดัชนีอย่างไร
ลองดูตัวอย่าง นี่คือตารางที่มีสามคอลัมน์ หนึ่งค่ามี 10 ค่าอีก 1,000 ค่าล่าสุด 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
ตัวเลขเหล่านี้จะถูกเติมด้วยเลขศูนย์ สิ่งนี้จะช่วยให้ประเด็นเกี่ยวกับการบีบอัดในภายหลัง
ดังนั้นคุณจึงมีคำถามทั่วไปสามข้อ:
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';
คุณทำดัชนีอะไร
ดัชนีเพียงเล็กน้อย __ จะดีกว่าการสแกนแบบเต็มตาราง:
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 )
คำถามซ่อนเงื่อน!
คำตอบคือไม่ใช่
แน่ใจว่าสามมิติเป็นสตริงที่มีความยาว ดังนั้นคุณสามารถบีบอัดได้ดี และคุณ (อาจ) ได้รับการข้ามดัชนีสแกนเพื่อค้นหาโดยใช้ (2-3_vals, lots_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 IOs แต่มันก็คุ้มค่าถ้ามีดัชนีสองตัวสำหรับการออมนี้?
และยังมีปัญหาอีกอย่างกับดัชนีคอมโพสิต เปรียบเทียบปัจจัยการจัดกลุ่มสำหรับดัชนีสามรายการซึ่งรวมถึง 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 นั้นสูงกว่า10xและ lots_few มากกว่า10 เท่า ! และนี่คือตารางสาธิตที่มีการจัดกลุ่มที่สมบูรณ์แบบเริ่มต้น ในฐานข้อมูลโลกแห่งความเป็นจริงผลกระทบมีแนวโน้มที่จะแย่ลง
ดังนั้นสิ่งที่ไม่ดีเกี่ยวกับที่?
ปัจจัยการจัดกลุ่มเป็นหนึ่งในตัวขับเคลื่อนหลักที่กำหนดว่าดัชนี "น่าสนใจ" เป็นอย่างไร ยิ่งมีโอกาสสูงที่เครื่องมือเพิ่มประสิทธิภาพก็จะเลือกน้อยลง โดยเฉพาะอย่างยิ่งถ้า lots_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 IOs จริงๆ
1: ในบางกรณีอาจมีค่ารวมถึงคอลัมน์ในดัชนีถ้านี่หมายความว่าคอลัมน์ทั้งหมดในแบบสอบถามของคุณอยู่ในดัชนี สิ่งนี้เปิดใช้งานการสแกนดัชนีเท่านั้นดังนั้นคุณไม่จำเป็นต้องเข้าถึงตาราง
2: หากคุณได้รับใบอนุญาตสำหรับการวินิจฉัยและการปรับแต่งคุณสามารถบังคับให้มีการข้ามการสแกนด้วย SQL Plan Management
ADDEDNDA
PS - เอกสารที่คุณยกมามีตั้งแต่ 9i นั่นคืออายุ reeeeeeally ฉันจะยึดติดกับบางสิ่งที่ใหม่กว่า