Showing posts with label stored outline. Show all posts
Showing posts with label stored outline. Show all posts

Friday, July 31, 2009

Function-Based IndexとOr-Expansionの制約

下のリンクに悲しい話があります。


http://forums.oracle.com/forums/message.jspa?messageID=3661603


  1. Standardエディションを使っているし
  2. Standardエディションは1)Bitmap Indexとその兄弟と言える2)Index Combinationを支援しません。
  3. Function-based Indexを使っているし
  4. OR Predicateを使っています。
  5. 一番大きい問題はSQL文章を変えることができないと言うことです。Softwareに含まれているQueryと言う理由で。。。


私がなぜこれを悲しい話と言うのかは下のデモを見れば理解できるんでしょう。


1. オブジェクトを作ります。


drop table t1 purge;

create table t1(c1 int, c2 int, c3 int);

insert into t1
select level, level, level
from dual
connect by level <= 100000;

create index t1_n1 on t1(c1+1); -- function-based index
create index t1_n2 on t1(c2+1); -- function-based index
create index t1_n3 on t1(c1); -- normal index
create index t1_n4 on t1(c2); -- normal index

exec dbms_stats.gather_table_stats(user, 't1');


2.Index Combinationが適用できる場合は次のようにとても効率的な実行計画を作ります。

alter session set "_b_tree_bitmap_plans" = true;

explain plan for
select *
from t1
where c1+1 = 1 or c2+1 = 1
;

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 48 | 2 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID | T1 | 2 | 48 | 2 (0)|
| 2 | BITMAP CONVERSION TO ROWIDS | | | | |
| 3 | BITMAP OR | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS| | | | |
|* 5 | INDEX RANGE SCAN | T1_N1 | | | 1 (0)|
| 6 | BITMAP CONVERSION FROM ROWIDS| | | | |
|* 7 | INDEX RANGE SCAN | T1_N2 | | | 1 (0)|
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - access("C1"+1=1)
7 - access("C2"+1=1)



3. でも、Index Combinationが非活性化すれば?

alter session set "_b_tree_bitmap_plans" = false; -- In standard edition, this would be fixed behavior.

explain plan for
select *
from t1
where c1+1 = 1 or c2+1 = 1
;

---------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 48 | 99 (6)|
|* 1 | TABLE ACCESS FULL| T1 | 2 | 48 | 99 (6)|
---------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("C1"+1=1 OR "C2"+1=1)


驚くべきことにFull Table Scanを選択してしまいます。


4.Function-based Indexではない一般Indexの場合には次善の策でOr-Expansionを選択します。これだけでも十分に効率的です。


explain plan for
select *
from t1
where c1 = 1 or c2 = 1
;

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 30 | 4 (0)|
| 1 | CONCATENATION | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 15 | 2 (0)|
|* 3 | INDEX RANGE SCAN | T1_N4 | 1 | | 1 (0)|
|* 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 15 | 2 (0)|
|* 5 | INDEX RANGE SCAN | T1_N3 | 1 | | 1 (0)|
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("C2"=1)
4 - filter(LNNVL("C2"=1))
5 - access("C1"=1)


5. では、次のようにヒントを通じて完璧に制御すればどうでしょうか。

explain plan for
select
/*+
INDEX_RS_ASC(@"SEL$1_2" "T1"@"SEL$1_2" "T1_N1")
INDEX_RS_ASC(@"SEL$1_1" "T1"@"SEL$1" "T1_N2")
USE_CONCAT(@"SEL$1" 8) */
*
from t1
where c1+1 = 1 or c2+1 = 1
;

----------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 48 | 195 (4)|
| 1 | CONCATENATION | | | | |
|* 2 | TABLE ACCESS FULL| T1 | 1 | 24 | 98 (5)|
|* 3 | TABLE ACCESS FULL| T1 | 1 | 24 | 98 (5)|
----------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("C2"+1=1)
3 - filter("C1"+1=1 AND LNNVL("C2"+1=1))


やはり無駄です。


なぜこんな現状が起きるんでしょうか。下の文章によく説明されています。

http://download.oracle.com/docs/cd/B14117_01/appdev.101/b10795/adfns_in.htm#1006464


불행하게도 Oracle은 Or-Expansion과 Funtion-based Index를 같이 사용하지 못합니다. 따라서 이 경우에는 Full Table Scan이 가장 효율적인 실행 계획이 되어 버린 셈입니다.
すなわち、オラクルはOr-ExpansionとFunction-based Indexを一緒に使えません。従って、この場合にはFull Table Scanが一番効率的な実行計画になってしまいました。


6. 試しできるトリックの一つは手動でStored Outlineを作るのです。でも、この場合にはそれさえも不可能です。ヒントで制御できないからです。


7. 最後のトリックはAdvanced Query Rewriting機能を使ってSQL文章自体を変えてしまうということです。ただし、この機能は次のような制約を持ってあります。


  • やはりEnterpriseエディション
  • Select文章だけ支援します。
  • バインド変数は支援しません。

上の簡単なデモの場合にはこの機能を使って解決できません。

begin
sys.dbms_advanced_rewrite.declare_rewrite_equivalence (
name => 'rewrite1',
source_stmt =>
'select *
from t1
where c1+1 = 1 or c2+1 = 1',
destination_stmt =>
'select *
from t1
where c1 = 0 or c2 = 0',
validate => false,
rewrite_mode => 'text_match');
end;
/

alter session set query_rewrite_integrity = trusted;

explain plan for
select *
from t1
where c1+1 = 1 or c2+1 = 1
;

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 30 | 4 (0)|
| 1 | CONCATENATION | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 15 | 2 (0)|
|* 3 | INDEX RANGE SCAN | T1_N4 | 1 | | 1 (0)|
|* 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 15 | 2 (0)|
|* 5 | INDEX RANGE SCAN | T1_N3 | 1 | | 1 (0)|
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("C2"=0)
4 - filter(LNNVL("C2"=0))
5 - access("C1"=0)

しかし、実際の本番環境では使用するのにはあまり制約が多いと思います。

Friday, July 24, 2009

Stored Outlineが働かない-オラクル11g

仲間の中の一人が次のようなオラクル11gの非正常的なStored Outlineに対するテスト結果を送りました。

1. オブジェクトを作ります。

create table t1(c1 int, c2 int);

-- c1 = skewed, c2 = normal
insert into t1
select 1, level
from dual
connect by level <= 10000
union all
select 2, level
from dual
connect by level <= 1000
union all
select 3, level
from dual
connect by level <= 100
union all
select 4, level
from dual
connect by level <= 10
union all
select 5, level
from dual
connect by level <= 1;

create index t1_n1 on t1(c1);
create index t1_n2 on t1(c2);

exec dbms_stats.gather_table_stats(user, 't1', method_opt => 'for columns c1 size skewonly');


2. 次のような実行計画を見えています。オラクル10gR2とオラクル11gが同じ実行計画を持っています。オラクルがT1_N1索引を選択したのに注意してください。

explain plan for
select /*+ gather_plan_statistics */ count(*)
from t1
where c1 = 4
and c2 between 1 and 10;

--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 2 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 6 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | T1_N1 | 10 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------


3. 現在の実行計画をTEST_OUTLN4のStored Outlineに貯蔵しますし、オラクルがこのOutlineを使うようにします。

create or replace outline test_outln4
on
select /*+ gather_plan_statistics */ count(*)
from t1
where c1 = 4
and c2 between 1 and 10;

alter session set use_stored_outlines = true;

TEST_OUTLN4のOutlineは次のようなヒントで構成されています。

select hint from user_outline_hints
where name = 'TEST_OUTLN4';

-- Oracle 10.2.0.1
HINT
--------------------------------------------------
INDEX(@"SEL$1" "T1"@"SEL$1" ("T1"."C1"))
OUTLINE_LEAF(@"SEL$1")
ALL_ROWS
OPTIMIZER_FEATURES_ENABLE('10.2.0.1')
IGNORE_OPTIM_EMBEDDED_HINTS

-- Oracle 11.1.0.6
HINT
------------------------------------------------
INDEX_RS_ASC(@"SEL$1" "T1"@"SEL$1" ("T1"."C1"))
OUTLINE_LEAF(@"SEL$1")
ALL_ROWS
DB_VERSION('11.1.0.6')
OPTIMIZER_FEATURES_ENABLE('11.1.0.6')
IGNORE_OPTIM_EMBEDDED_HINTS


4. ここから変なことができます。次のように3番目の索引T1_N3を追加します。

-- t1_n3 index (c1, c2)
create index t1_n3 on t1(c1, c2);


5. オラクル10gR2は私が作ったOutlineをよく従います。

explain plan for
select /*+ gather_plan_statistics */ count(*)
from t1
where c1 = 4
and c2 between 1 and 10;



--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 2 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 6 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | T1_N1 | 10 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------

Note
-----
- outline "TEST_OUTLN4" used for this statement


6. しかし、オラクル11gはOutlineを従わなく、さっきの索引T1_N3を選択してしまいます。もっとおかしいものはオラクル自分は私はTEST_OUTLN4をよく使っていると話しているのです。

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | INDEX RANGE SCAN| T1_N3 | 1 | 6 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
- outline "TEST_OUTLN4" used for this statement


7. 理由は?今は私もよくわかりません。でも、10053トレースが少しの追加的な情報を提供します。

オラクル10gR2のトレースは明らかにOutlineによってINDEXヒントを使っていると記録しています。

BASE STATISTICAL INFORMATION
***********************
Table Stats::
Table: T1 Alias: T1
#Rows: 11111 #Blks: 20 AvgRowLen: 6.00
Index Stats::
Index: T1_N1 Col#: 1
LVLS: 1 #LB: 22 #DK: 5 LB/K: 4.00 DB/K: 4.00 CLUF: 21.00
User hint to use this index -- Look at this part!
Index: T1_N2 Col#: 2
LVLS: 1 #LB: 24 #DK: 10000 LB/K: 1.00 DB/K: 1.00 CLUF: 2116.00
Index: T1_N3 Col#: 1 2
LVLS: 1 #LB: 28 #DK: 11111 LB/K: 1.00 DB/K: 1.00 CLUF: 19.00

でも、オラクル11gのトレースはOutlineによって与えたヒントに対する情報が全然ありません。

BASE STATISTICAL INFORMATION
***********************
Table Stats::
Table: T1 Alias: T1
#Rows: 11111 #Blks: 20 AvgRowLen: 6.00
Index Stats::
Index: T1_N1 Col#: 1
LVLS: 1 #LB: 22 #DK: 5 LB/K: 4.00 DB/K: 4.00 CLUF: 21.00
Index: T1_N2 Col#: 2
LVLS: 1 #LB: 24 #DK: 10000 LB/K: 1.00 DB/K: 1.00 CLUF: 2116.00
Index: T1_N3 Col#: 1 2
LVLS: 1 #LB: 28 #DK: 11111 LB/K: 1.00 DB/K: 1.00 CLUF: 19.00
Access path analysis for T1


これはバグなのか、もしくは知られない新機能なのかわかりませんけど。。。私には馬鹿なバグに見えますが。。。