<p>
组合索引(concatenated index):由多个列构成的索引,如create index idx_emp on emp(col1, col2, col3, ……),则我们称idx_emp索引为组合索引。
</p>
<p>
在组合索引中有一个重要的概念:引导列(leading column),在上面的例子中,col1列为引导列。当我们进行查询时可以使用”where col1 = ? ”,也可以使用”where col1 = ? and col2 = ?”,这样的限制条件都会使用索引,但是”where col2 = ? ”查询就不会使用该索引。所以限制条件中包含先导列时,该限制条件才会使用该组合索引。
</p>
<p>
<br />
</p>
<p>
有这样一个SQL <br />
select count(*) from t1,t2 where t1.id=t2.id and t1.owner='SCOTT'; <br />
id列选择性很高,owner选择性很低 <br />
要优化它很简单,只需要在t1表上建立一个组合索引(owner,id),在t2表上建立一个索引(id) <br />
现在要讨论的是我们应该怎么建立组合索引,哪一列(owner,id)应该放在最前面? <br />
<br />
现在来做个实验 <br />
<br />
SQL> desc t1 <br />
Name Type Nullable Default Comments <br />
----------- ------------- -------- ------- -------- <br />
ID NUMBER Y <br />
OBJECT_NAME VARCHAR2(128) Y <br />
OWNER VARCHAR2(30) Y <br />
<br />
SQL> desc t2 <br />
Name Type Nullable Default Comments <br />
--------- ----------- -------- ------- -------- <br />
ID NUMBER Y <br />
STATUS VARCHAR2(7) Y <br />
TEMPORARY VARCHAR2(1) Y <br />
<br />
<br />
SQL> create index inx_id on t2(id); <br />
<br />
Index created. <br />
<br />
SQL> create index inx_id_owner on t1(id,owner); <br />
<br />
Index created. <br />
<br />
SQL> select count(*) from t1,t2 where t1.id=t2.id and t1.owner='SCOTT'; <br />
<br />
Elapsed: 00:00:00.02 <br />
<br />
Execution Plan <br />
---------------------------------------------------------- <br />
Plan hash value: 2432674005 <br />
<br />
--------------------------------------------------------------------------------------- <br />
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | <br />
--------------------------------------------------------------------------------------- <br />
| 0 | SELECT STATEMENT | | 1 | 16 | 88 (2)| 00:00:02 | <br />
| 1 | SORT AGGREGATE | | 1 | 16 | | | <br />
|* 2 | HASH JOIN | | 2416 | 38656 | 88 (2)| 00:00:02 | <br />
|* 3 | INDEX FAST FULL SCAN| INX_ID_OWNER | 2416 | 26576 | 50 (0)| 00:00:01 | <br />
| 4 | INDEX FAST FULL SCAN| INX_ID | 72475 | 353K| 37 (0)| 00:00:01 | <br />
--------------------------------------------------------------------------------------- <br />
<br />
Predicate Information (identified by operation id): <br />
--------------------------------------------------- <br />
<br />
2 - access("T1"."ID"="T2"."ID") <br />
3 - filter("T1"."OWNER"='SCOTT') <br />
<br />
<br />
Statistics <br />
---------------------------------------------------------- <br />
0 recursive calls <br />
0 db block gets <br />
392 consistent gets <br />
0 physical reads <br />
0 redo size <br />
422 bytes sent via SQL*Net to client <br />
420 bytes received via SQL*Net from client <br />
2 SQL*Net roundtrips to/from client <br />
0 sorts (memory) <br />
0 sorts (disk) <br />
1 rows processed <br />
<br />
SQL> create index inx_owner_id on t1(owner,id); <br />
<br />
Index created. <br />
<br />
SQL> select count(*) from t1,t2 where t1.id=t2.id and t1.owner='SCOTT'; <br />
<br />
Elapsed: 00:00:00.03 <br />
<br />
Execution Plan <br />
---------------------------------------------------------- <br />
Plan hash value: 277464349 <br />
<br />
--------------------------------------------------------------------------------------- <br />
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | <br />
--------------------------------------------------------------------------------------- <br />
| 0 | SELECT STATEMENT | | 1 | 16 | 47 (3)| 00:00:01 | <br />
| 1 | SORT AGGREGATE | | 1 | 16 | | | <br />
|* 2 | HASH JOIN | | 2416 | 38656 | 47 (3)| 00:00:01 | <br />
|* 3 | INDEX RANGE SCAN | INX_OWNER_ID | 2416 | 26576 | 9 (0)| 00:00:01 | <br />
| 4 | INDEX FAST FULL SCAN| INX_ID | 72475 | 353K| 37 (0)| 00:00:01 | <br />
--------------------------------------------------------------------------------------- <br />
<br />
Predicate Information (identified by operation id): <br />
--------------------------------------------------- <br />
<br />
2 - access("T1"."ID"="T2"."ID") <br />
3 - access("T1"."OWNER"='SCOTT') <br />
<br />
<br />
Statistics <br />
---------------------------------------------------------- <br />
0 recursive calls <br />
0 db block gets <br />
169 consistent gets <br />
0 physical reads <br />
0 redo size <br />
422 bytes sent via SQL*Net to client <br />
420 bytes received via SQL*Net from client <br />
2 SQL*Net roundtrips to/from client <br />
0 sorts (memory) <br />
0 sorts (disk) <br />
1 rows processed <br />
<br />
如果OWNER作为引导列,逻辑读由以前的392变成现在的169,并且由以前的 index fast full scan 变成index range scan <br />
<br />
如果强制指定走索引 inx_id_owner <br />
<br />
SQL> select /*+ index(t1 inx_id_owner) */ count(*) from t1,t2 where t1.id=t2.id and t1.owner='SCOTT'; <br />
<br />
Elapsed: 00:00:00.03 <br />
<br />
Execution Plan <br />
---------------------------------------------------------- <br />
Plan hash value: 3161475902 <br />
<br />
--------------------------------------------------------------------------------------- <br />
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | <br />
--------------------------------------------------------------------------------------- <br />
| 0 | SELECT STATEMENT | | 1 | 16 | 259 (1)| 00:00:04 | <br />
| 1 | SORT AGGREGATE | | 1 | 16 | | | <br />
|* 2 | HASH JOIN | | 2416 | 38656 | 259 (1)| 00:00:04 | <br />
|* 3 | INDEX FULL SCAN | INX_ID_OWNER | 2416 | 26576 | 221 (1)| 00:00:03 | <br />
| 4 | INDEX FAST FULL SCAN| INX_ID | 72475 | 353K| 37 (0)| 00:00:01 | <br />
--------------------------------------------------------------------------------------- <br />
<br />
Predicate Information (identified by operation id): <br />
--------------------------------------------------- <br />
<br />
2 - access("T1"."ID"="T2"."ID") <br />
3 - access("T1"."OWNER"='SCOTT') <br />
filter("T1"."OWNER"='SCOTT') <br />
<br />
<br />
Statistics <br />
---------------------------------------------------------- <br />
0 recursive calls <br />
0 db block gets <br />
387 consistent gets <br />
0 physical reads <br />
0 redo size <br />
422 bytes sent via SQL*Net to client <br />
420 bytes received via SQL*Net from client <br />
2 SQL*Net roundtrips to/from client <br />
0 sorts (memory) <br />
0 sorts (disk) <br />
1 rows processed <br />
<br />
依然要387个逻辑读。 <br />
<br />
为什么要以owner为引导列?因为ID是join列,并且ID列上面没过滤条件,如果以ID列作为引导列,由于没过滤条件 <br />
那么CBO只能走 index full scan,或者index fast full scan,因为引导列没过滤条件,走不了index range scan, <br />
最多走index skip scan,不过index skip scan代价过高,因为index skip scan要求 引导列选择性很低,但是ID这里选择性很高 <br />
<br />
SQL> select /*+ index_ss(t1 inx_id_owner) */ count(*) from t1,t2 where t1.id=t2.id and t1.owner='SYS'; <br />
<br />
Elapsed: 00:00:00.10 <br />
<br />
Execution Plan <br />
---------------------------------------------------------- <br />
Plan hash value: 3493079762 <br />
<br />
--------------------------------------------------------------------------------------- <br />
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | <br />
--------------------------------------------------------------------------------------- <br />
| 0 | SELECT STATEMENT | | 1 | 16 | 72529 (1)| 00:14:31 | <br />
| 1 | SORT AGGREGATE | | 1 | 16 | | | <br />
|* 2 | HASH JOIN | | 2416 | 38656 | 72529 (1)| 00:14:31 | <br />
|* 3 | INDEX SKIP SCAN | INX_ID_OWNER | 2416 | 26576 | 72491 (1)| 00:14:30 | <br />
| 4 | INDEX FAST FULL SCAN| INX_ID | 72475 | 353K| 37 (0)| 00:00:01 | <br />
--------------------------------------------------------------------------------------- <br />
<br />
Predicate Information (identified by operation id): <br />
--------------------------------------------------- <br />
<br />
2 - access("T1"."ID"="T2"."ID") <br />
3 - access("T1"."OWNER"='SYS') <br />
filter("T1"."OWNER"='SYS') <br />
<br />
<br />
Statistics <br />
---------------------------------------------------------- <br />
1 recursive calls <br />
0 db block gets <br />
387 consistent gets <br />
0 physical reads <br />
0 redo size <br />
424 bytes sent via SQL*Net to client <br />
420 bytes received via SQL*Net from client <br />
2 SQL*Net roundtrips to/from client <br />
0 sorts (memory) <br />
0 sorts (disk) <br />
1 rows processed <br />
<br />
如果owner列作为引导列,那么优化器就可以选择index range scan,这样相比index full scan, index fast full scan <br />
肯定要少扫描很多leaf block,逻辑读就会相对较少。 <br />
<br />
其实到这里,是否可以总结一下建立组合索引的原则呢? <br />
引导列要选择过滤条件的列作为引导列,比如 where a.xxx='xxx' 或者 a.xxx> 或者 a.xxx< <br />
引导列的选择性越高越好,因为选择性越高,扫描的leaf block就越少,效率就越高 <br />
尽量把join列放到组合索引最后面 <br />
<br />
这里选择以owner列作为引导列,由于owner选择性很低,所以测试索引压缩对于性能的提升 <br />
<br />
SQL> analyze index inx_owner_id validate structure; <br />
<br />
Index analyzed. <br />
<br />
SQL> select height, <br />
2 blocks, <br />
3 lf_blks, <br />
4 br_blks, <br />
5 OPT_CMPR_COUNT, <br />
6 OPT_CMPR_PCTSAVE <br />
7 from index_stats <br />
8 where name = 'INX_OWNER_ID'; <br />
<br />
HEIGHT BLOCKS LF_BLKS BR_BLKS OPT_CMPR_COUNT OPT_CMPR_PCTSAVE <br />
---------- ---------- ---------- ---------- -------------- ---------------- <br />
2 256 219 1 1 26 <br />
<br />
SQL> drop index inx_owner_id; <br />
<br />
Index dropped <br />
<br />
SQL> create index inx_owner_id on t1(owner,id) compress 1; <br />
<br />
Index created <br />
<br />
SQL> analyze index inx_owner_id validate structure; <br />
<br />
Index analyzed <br />
<br />
SQL> <br />
SQL> select height, <br />
2 blocks, <br />
3 lf_blks, <br />
4 br_blks, <br />
5 OPT_CMPR_COUNT, <br />
6 OPT_CMPR_PCTSAVE <br />
7 from index_stats <br />
8 where name = 'INX_OWNER_ID'; <br />
<br />
HEIGHT BLOCKS LF_BLKS BR_BLKS OPT_CMPR_COUNT OPT_CMPR_PCTSAVE <br />
---------- ---------- ---------- ---------- -------------- ---------------- <br />
2 256 161 1 1 0 <br />
<br />
索引压缩之后,Leaf block 由原来的219降低到161个,节约了58个block 现在再来看一看执行计划+统计信息 <br />
<br />
SQL> select count(*) from t1,t2 where t1.id=t2.id and t1.owner='SCOTT'; <br />
<br />
Elapsed: 00:00:00.03 <br />
<br />
Execution Plan <br />
---------------------------------------------------------- <br />
Plan hash value: 277464349 <br />
<br />
--------------------------------------------------------------------------------------- <br />
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | <br />
--------------------------------------------------------------------------------------- <br />
| 0 | SELECT STATEMENT | | 1 | 16 | 45 (3)| 00:00:01 | <br />
| 1 | SORT AGGREGATE | | 1 | 16 | | | <br />
|* 2 | HASH JOIN | | 2416 | 38656 | 45 (3)| 00:00:01 | <br />
|* 3 | INDEX RANGE SCAN | INX_OWNER_ID | 2416 | 26576 | 7 (0)| 00:00:01 | <br />
| 4 | INDEX FAST FULL SCAN| INX_ID | 72475 | 353K| 37 (0)| 00:00:01 | <br />
--------------------------------------------------------------------------------------- <br />
<br />
Predicate Information (identified by operation id): <br />
--------------------------------------------------- <br />
<br />
2 - access("T1"."ID"="T2"."ID") <br />
3 - access("T1"."OWNER"='SCOTT') <br />
<br />
<br />
Statistics <br />
---------------------------------------------------------- <br />
1 recursive calls <br />
0 db block gets <br />
169 consistent gets <br />
0 physical reads <br />
0 redo size <br />
422 bytes sent via SQL*Net to client <br />
420 bytes received via SQL*Net from client <br />
2 SQL*Net roundtrips to/from client <br />
0 sorts (memory) <br />
0 sorts (disk) <br />
1 rows processed <br />
<br />
由此可见,索引压缩之后,逻辑读并没有下降,还是169,但是索引的leaf blcok显著减少了,这样减少了存储空间,能降低物理IO
</p>
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31401608/viewspace-2148698/
,如需转载,请注明出处,否则将追究法律责任。