create index address_city_street_idx on address(city, street) compress 1 local;
:
select * from address where tenant = 'X' and street = 'Y' and city = 'Z'
質問1と2に答えるために:テナントがパーティションであるので、キーはこのインデックスに含まれてはならず、おそらくインデックスに含まれていないはずです。その列はすでにパーティションプルーニングによって関連するセグメントを選択するために使用されています。その作業はコンパイル時または解析時に行われ、事実上フリーです。
テストケースの実行計画は、パーティションプルーニングが行われていることを示しています。操作PARTITION LIST SINGLE
とKEY
のような変数の代わりに列Pstart
とPstop
が番号3をリストするという事実は、Oracleがすでに問合せを実行する前にパーティションを決定していることを示しています。オラクルはコンパイル時に無関係なTENANTを即時に破棄しているため、実行時にTENANTを索引でさらに減らす必要はありません。
私のインデックスの提案は、データに関するいくつかの前提に依存します。 CITYもSTREETも、テナントの行を一意に識別するようには聞こえません。 STREETはCITYよりはるかに選択的です。 1つのCITYに複数のSTREETがある場合は、その順序で索引付けし、索引圧縮を使用すると多くのスペースを節約できます。
インデックスが大幅に小さい場合、レベルが低下する可能性があります。つまり、ルックアップに必要なI/Oがわずかに減少します。それが小さくなるとバッファキャッシュに収まる可能性が高くなり、パフォーマンスがさらに向上する可能性があります。
テーブルが大きければ、私はBLEVEL(インデックスレベルの数)が両方で同じになり、両方のインデックスがキャッシュを効果的に使用するには大きすぎると感じています。つまり、(CITY,STREET)
と(STREET,CITY)
の間にパフォーマンスの差異がないことを意味します。しかし、(CITY,STREET)
と圧縮では、少なくとも大量の領域を節約できます。
テストケース
私は、あなたは、単に生産の両方のインデックスを作成し、それらを試してみることができないと仮定します。その場合、最初にいくつかのテストを作成したいと思うでしょう。
このテストケースは、私の提案を強く支持するものではありません。これは、より完全なテストケースの出発点にすぎません。大量のデータとより現実的なデータの分布を持つものを作成する必要があります。
--Create sample table.
create table address
(
id number,
street varchar2(100),
city varchar2(100),
state varchar2(100),
tenant varchar2(100)
) partition by list (tenant)
(
partition p1 values ('tenant1'),
partition p2 values ('tenant2'),
partition p3 values ('tenant3'),
partition p4 values ('tenant4'),
partition p5 values ('tenant5')
) nologging;
--Insert 5M rows.
--Note the assumptions about the selectivity of the street and city
--are critical to this issue. Adjust the MOD as necessary.
begin
for i in 1 .. 5 loop
insert /*+ append */ into address
select
level,
'Fake Street '||mod(level, 10000),
'City '||mod(level, 100),
'State',
'tenant'||i
from dual connect by level <= 1000000;
commit;
end loop;
end;
/
--Table uses 282MB.
select sum(bytes)/1024/1024 mb from dba_segments where segment_name = 'ADDRESS' and owner = user;
--Create different indexes.
create index address_city_street_idx on address(city, street) compress 1 local;
create index address_street_city_idx on address(street, city) local;
--Gather statistics.
begin
dbms_stats.gather_table_stats(user, 'ADDRESS');
end;
/
--Check execution plan.
--Oracle by default picks STREET,CITY over CITY,STREET.
--I'm not sure why. And the cost difference is only 1, so I think things may be different with realistic data.
explain plan for select * from address where tenant = 'tenant3' and street = 'Fake Street 50' and city = 'City 50';
select * from table(dbms_xplan.display);
/*
Plan hash value: 2845844304
--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 4 (0)| 00:00:01 | | |
| 1 | PARTITION LIST SINGLE | | 1 | 44 | 4 (0)| 00:00:01 | 3 | 3 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| ADDRESS | 1 | 44 | 4 (0)| 00:00:01 | 3 | 3 |
|* 3 | INDEX RANGE SCAN | ADDRESS_STREET_CITY_IDX | 1 | | 3 (0)| 00:00:01 | 3 | 3 |
--------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("STREET"='Fake Street 50' AND "CITY"='City 50')
*/
--Check execution plan of forced CITY,STREET index.
--I don't suggest using a hint in the real query, this is just to compare plans.
explain plan for select /*+ index(address address_city_street_idx) */ * from address where tenant = 'tenant3' and street = 'Fake Street 50' and city = 'City 50';
select * from table(dbms_xplan.display);
/*
Plan hash value: 1084849450
--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 5 (0)| 00:00:01 | | |
| 1 | PARTITION LIST SINGLE | | 1 | 44 | 5 (0)| 00:00:01 | 3 | 3 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| ADDRESS | 1 | 44 | 5 (0)| 00:00:01 | 3 | 3 |
|* 3 | INDEX RANGE SCAN | ADDRESS_CITY_STREET_IDX | 1 | | 3 (0)| 00:00:01 | 3 | 3 |
--------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("CITY"='City 50' AND "STREET"='Fake Street 50')
*/
--Both indexes have BLEVEL=2.
select *
from dba_indexes
where index_name in ('ADDRESS_CITY_STREET_IDX', 'ADDRESS_STREET_CITY_IDX');
--CITY,STREET = 160MB, STREET,CITY=200MB.
--You can see the difference already. It may get larger with different data distribution.
--And it may get larger with more data, as it may compress better with more repetition.
select segment_name, sum(bytes)/1024/1024 mb
from dba_segments
where segment_name in ('ADDRESS_CITY_STREET_IDX', 'ADDRESS_STREET_CITY_IDX')
group by segment_name;
各パーティションにはいくつの異なるテナントがありますか? LISTパーティションを使用しているので、それほど多くないと仮定します。この場合、テナントのインデックスは無駄です(単一のインデックスまたはコンポジットインデックスの一部であるかどうかに関係なく) –
いいえ、クエリでは1つ以上のインデックスを使用できます。しかし、B *ツリー索引では、これはめったに起きません。しかし、ビットマップインデックスは、デザインによって組み合わせて使用されます。 –
@WernfriedDomscheitパーティションごとに1つのテナントが存在するのは、テナントがパーティション化されているためです。いくつかのテナントが約500あるので、いくつかのパーティションがあります。 – AHungerArtist