在我自己的實驗結果中,DB計算的cost全表竟然比標題中任何一種index join方法都要低很多倍,但實際執行速度卻要慢很多倍,當然這裡沒有考慮數據傾斜等因素
因此,我找到自己問題的答案------為什麼在一個語句的表中,明明有多個索引可以同時使用(join),增加效率及篩選,而oracle卻只能使用其中一個索引,這是因為oracle執行路徑的cost作怪,這時用hit可以明顯提高執行效率
10g中sql profile是一個很好的工具,OEM中提供了很方便的介面,它能執行的最優執行路徑和hit超乎我的想象,可以作為優化的參考工具
第一篇
Oracle Index Merge与and_equal的变迁
作者:eygle来源:Oracle Life时间:2010-06-21 17:30:28人气:157评论:0and_equal是Oracle支持的一种特定操作,可以将多个单列索引进行合并(Index Merge)输出查询结果。
同时and_equal可以通过Hints来进行强制,最少指定两个索引,最多指定5个。
以下的执行计划是常见的and_equal执行方式:
SQL> select /*+ and_equal(t1 iu ii) */ username,password from t1 where username='EYGLE' and user_id=58;
USERNAME PASSWORD
------------------------------ ------------------------------
EYGLE B726E09FE21F8E83执行计划
----------------------------------------------------------
Plan hash value: 1425017857
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 47 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 47 | 2 (0)| 00:00:01 |
| 2 |AND-EQUAL | | | | | |
|* 3 | INDEX RANGE SCAN | II | 1 | | 1 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IU | 1 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("USERNAME"='EYGLE' AND "USER_ID"=58)
3 - access("USER_ID"=58)
4 - access("USERNAME"='EYGLE')
但是从Oracle 10g开始,and_equal操作被废弃(depricated)掉,Oracle不再支持。这里废弃的含义并不是被彻底移除,而是说不再进行改进,通过hints仍然可以强制实现Index Merge的and_equal操作。
以上的输出就是来自Oracle 10.2.0.4,在使用rule提示时,Oracle会主动选择and_equal,所以不再支持是因为,这种方法限制过多,而且远不如复合索引来的灵活:
SQL> select * from v$version;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Prod
PL/SQL Release 10.2.0.4.0 - Production
CORE 10.2.0.4.0 Production
TNS for 32-bit Windows: Version 10.2.0.4.0 - Production
NLSRTL Version 10.2.0.4.0 - Production
SQL> select /*+ rule */ username,password from t1 where username='EYGLE' and user_id=58;
USERNAME PASSWORD
------------------------------ ------------------------------
EYGLE B726E09FE21F8E83执行计划
----------------------------------------------------------
Plan hash value: 3072843751
--------------------------------------------
| Id | Operation | Name |
--------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| T1 |
| 2 | AND-EQUAL | |
|* 3 | INDEX RANGE SCAN | IU |
|* 4 | INDEX RANGE SCAN | II |
--------------------------------------------
新的可选替代方案是NDEX_COMBINE,从9i开始,初始化参数_b_tree_bitmap_plans设置为true,允许为B*Tree索引使用,bitmap convert:
SQL> select /*+ index_combine(t1 iu ii) */ username,password from t1 where username='EYGLE' and user_id=58;
USERNAME PASSWORD
------------------------------ ------------------------------
EYGLE B726E09FE21F8E83执行计划
----------------------------------------------------------
Plan hash value: 1808973554
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 47 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | T1 | 1 | 47 | 2 (0)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP AND | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 5 | INDEX RANGE SCAN | II | | | 1 (0)| 00:00:01 |
| 6 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 7 | INDEX RANGE SCAN | IU | | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("USER_ID"=58)
7 - access("USERNAME"='EYGLE')
第二篇
Posted inOracleat 7:30下午 由 wanghai
and_equal,index_join,index_combine这三种都是oracle利用索引关联获得数据的方法,三者的目的都是为了最大限度的利用索引,减少回表的代价.但是三者的实现方法是有区别的,下面一一来分析.
and_equal:
这种方式需要查询条件里面包括所有索引列,然后取得每个索引中得到的rowid列表,然后对这些列表做merge join,过滤出相同的rowid后再去表中获取数据或者直接从索引中获得数据.and_equal有一些限制,比如它只对单列索引有效,只对非唯一索引 有效,使用到的索引不能超过5个,查询条件只能是”=”.在10g中,and_equal已经被废弃了,只能通过hint才能生效.
create table test as select * from dba_objects;
create index ind_test_owner on test(owner);
create index ind_test_object_name on test(object_name);
SQL 10G>select/*+ and_equal(test ind_test_owner ind_test_object_name)*/ owner,object_name from test where wner=’test’ and object_name=’test’;
Execution Plan
———————————————————-
——————————————————————————-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
——————————————————————————-
| 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)|
|* 1 | AND-EQUAL | | | | |
|* 2 | INDEX RANGE SCAN| IND_TEST_OWNER | 1 | | 1 (0)|
|* 3 | INDEX RANGE SCAN| IND_TEST_OBJECT_NAME | 2 | | 1 (0)|
——————————————————————————-
如果查询条件只包含owner
SQL 10G>select/*+ and_equal(test ind_test_owner ind_test_object_name)*/ owner,object_name from test where wner=’test’;
Execution Plan
———————————————————-
———————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|———————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 29 | 2 (0)|
|* 2 | INDEX RANGE SCAN | IND_TEST_OWNER | 1 | | 1 (0)|———————————————————————————–
修改owner,object_name为非空
alter table test modify(owner not null);
alter table test modify(object_name not null);
SQL 10G>select/*+ and_equal(test ind_test_owner ind_test_object_name)*/ owner,object_name from test where wner=’test’;
Execution Plan
———————————————————-
———————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|———————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 29 | 2 (0)|
|* 2 | INDEX RANGE SCAN | IND_TEST_OWNER | 1 | | 1 (0)|———————————————————————————–
效果一样
查询条件是”>”的情况
SQL 10G>select/*+ and_equal(test ind_test_owner ind_test_object_name)*/ owner,object_name,object_type from test where owner>’test’ and object_name=’test’;
Execution Plan
———————————————————-
—————————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|—————————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 40 | 2 (0)|
|* 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 40 | 2 (0)|
|* 2 | INDEX RANGE SCAN | IND_TEST_OBJECT_NAME | 2 | | 1 (0)|—————————————————————————————–
查询条件是in的情况
SQL 10G>select/*+ and_equal(test ind_test_owner ind_test_object_name)*/ owner,object_name,object_type from test where owner in(’test’,'dba’) and object_name=’test’;
Execution Plan
———————————————————-
—————————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|—————————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 40 | 2 (0)|
|* 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 40 | 2 (0)|
|* 2 | INDEX RANGE SCAN | IND_TEST_OBJECT_NAME | 2 | | 1 (0)|—————————————————————————————–
再来看一下回表的情况
SQL 10G>select/*+ and_equal(test ind_test_owner ind_test_object_name)*/ owner,object_name,object_type from test where wner=’test’ and object_name=’test’;
Execution Plan
———————————————————-
—————————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|—————————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 40 | 2 (0)|
|* 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 40 | 2 (0)|
| 2 | AND-EQUAL | | | | |
|* 3 | INDEX RANGE SCAN | IND_TEST_OWNER | 1 | | 1 (0)|
|* 4 | INDEX RANGE SCAN | IND_TEST_OBJECT_NAME | 2 | | 1 (0)|—————————————————————————————–
先通过and_equal取得rowid列表,然后从表中返回数据.
index_join:
index join顾名思义是对index进行关联,oracle通过hash index join的方式实现了避免对表的访问.所有的数据都从索引中直接获得.它不受查询条件影响,可以是唯一索引,也可以是多列索引.
SQL 10G>select/*+ index_join(test ind_test_owner ind_test_object_name)*/ owner,object_name from test where wner=’test’ and object_name=’test’;
Execution Plan
———————————————————-
——————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|——————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 29 | 3 (34)|
|* 1 | VIEW | index$_join$_001 | 1 | 29 | 3 (34)|
|* 2 | HASH JOIN | | | | |
|* 3 | INDEX RANGE SCAN| IND_TEST_OWNER | 1 | 29 | 1 (0)|
|* 4 | INDEX RANGE SCAN| IND_TEST_OBJECT_NAME | 1 | 29 | 1 (0)|——————————————————————————–
可以不带查询条件,只不过由index range scan变成了index fast full scan
SQL 10G>select/*+ index_join(test ind_test_owner ind_test_object_name)*/ owner,object_name from test
2 ;
Execution Plan
———————————————————-
————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|————————————————————————————
| 0 | SELECT STATEMENT | | 51984 | 1472K| 478 (2)|
| 1 | VIEW | index$_join$_001 | 51984 | 1472K| 478 (2)|
|* 2 | HASH JOIN | | | | |
| 3 | INDEX FAST FULL SCAN| IND_TEST_OWNER | 51984 | 1472K| 153 (2)|
| 4 | INDEX FAST FULL SCAN| IND_TEST_OBJECT_NAME | 51984 | 1472K| 322 (1)|————————————————————————————
如果不是所有数据都能从索引获得,那么将不会使用index join
SQL 10G>select/*+ index_join(test ind_test_owner ind_test_object_name)*/ owner,object_name,object_type from test where wner=’test’ and object_name=’test’;
Execution Plan
———————————————————-
———————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|———————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 40 | 2 (0)|
|* 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 40 | 2 (0)|
|* 2 | INDEX RANGE SCAN | IND_TEST_OWNER | 1 | | 1 (0)|———————————————————————————–
index_combine:
index combine最早是用在bitmap index上的,在9i开始oracle默认可以使用在btree索引上,这是由_b_tree_bitmap_plans参数来控制的.oracle将 btree索引中获得的rowid信息通过BITMAP CONVERSION FROM ROWIDS的步骤转换成bitmap进行匹配,然后匹配完成后通过BITMAP CONVERSION TO ROWIDS再转换出rowid获得数据或者回表获得数据.
SQL 10G>select/*+ index_combine(test ind_test_owner ind_test_object_name)*/ owner,object_name from test where wner=’test’ and object_name=’test’;
Execution Plan
———————————————————-
———————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|———————————————————————————————
| 0 | SELECT STATEMENT | | 1 | 29 | 2 (0)|
| 1 | BITMAP CONVERSION TO ROWIDS | | 1 | 29 | 2 (0)|
| 2 | BITMAP AND | | | | |
| 3 | BITMAP CONVERSION FROM ROWIDS| | | | |
|* 4 | INDEX RANGE SCAN | IND_TEST_OWNER | | | 1 (0)|
| 5 | BITMAP CONVERSION FROM ROWIDS| | | | |
|* 6 | INDEX RANGE SCAN | IND_TEST_OBJECT_NAME | | | 1 (0)|———————————————————————————————
回表取数据的情况
SQL 10G>select/*+ index_combine(test ind_test_owner ind_test_object_name)*/ owner,object_name,object_type from test where wner=’test’ and object_name=’test’;
Execution Plan
———————————————————-
———————————————————————————————-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
———————————————————————————————-
| 0 | SELECT STATEMENT | | 1 | 40 | 2 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID | TEST | 1 | 40 | 2 (0)|
| 2 | BITMAP CONVERSION TO ROWIDS | | | | |
| 3 | BITMAP AND | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS| | | | |
|* 5 | INDEX RANGE SCAN | IND_TEST_OWNER | | | 1 (0)|
| 6 | BITMAP CONVERSION FROM ROWIDS| | | | |
|* 7 | INDEX RANGE SCAN | IND_TEST_OBJECT_NAME | | | 1 (0)|
———————————————————————————————-
不带查询条件的情况,index combine将不被使用
SQL 10G>select/*+ index_combine(test ind_test_owner ind_test_object_name)*/ owner,object_name from test
2 ;
Execution Plan
———————————————————-
———————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|———————————————————————————–
| 0 | SELECT STATEMENT | | 51984 | 1472K| 1480 (1)|
| 1 | TABLE ACCESS BY INDEX ROWID| TEST | 51984 | 1472K| 1480 (1)|
| 2 | INDEX FULL SCAN | IND_TEST_OWNER | 51984 | | 123 (2)|———————————————————————————–
index_combine会是and_equal的很好的替代者,随着and_equal的退出,index_combine将更多得被我们看到.
我實驗的環境
CREATETABLESYSTEM.TEST
(
AINTEGER,
BINTEGER,
CVARCHAR2(1000BYTE)
);
CREATEINDEXSYSTEM.TEST_IDX1ONSYSTEM.TEST (A);
CREATEINDEXSYSTEM.TEST_IDX2ONSYSTEM.TEST (B);
insertintotestvalues(1,2,'abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgababcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefg');
insertintotestvalues(1,3,'abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgaabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgbcdefgabcdefgabcdefg');
insertintotestvalues(4,2,'abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgdefgabcdefg');
insertintotestvalues(11,11,'abcdefgabcdefgabcabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefg');
insertintotestselect*fromtestwherea=1andb=3;
commit;
--3 000 rows
insertintotestselect*fromtestwherea=4andb=2;
commit;
--6 0000 rows
insertintotestselect*fromtestwherea=11andb=11;
commit;
--100W,多插數據可以比較出執行時間
select*fromtestwherea=1andb=2–-只有一條記錄的結果集
分析前會走and_equal(
analyzeindextest_idx1computestatistics;
analyzeindextest_idx2computestatistics;
analyzetabletestcomputestatistics;
分析後比較一下它們的cost及執行效率
select*fromtestwherea=1andb=2
select/*+ index(test test_idx1) */*fromtestwherea=1andb=2
select/*+ and_equal(test test_idx1 test_idx2) */*fromtestwherea=1andb=2
select/*+ index_combine(test test_idx1 test_idx2) */*fromtestwherea=1andb=2
不同的環境,index_combine與and_equal的效率應該還是有些差異,因為其中有一個bitmap convert的過程