これは、ビットマップ インデックスの適切な候補のように見えます。
ビットマップ インデックスは主に、データ ウェアハウスやクエリがアドホックな方法で多数の列を参照する環境向けに設計されています。ビットマップ インデックスが必要になる状況には、次のようなものがあります。
インデックス付きの列のカーディナリティは低くなります。つまり、個別の値の数がテーブルの行の数に比べて少なくなります。
インデックス付きテーブルは読み取り専用であるか、DML ステートメントによる大幅な変更の対象ではありません。
具体的には、ここではビットマップ結合インデックスが理想的です。マニュアルの例は、データ モデルとも一致します。以下のモデルとデータを再作成しようとしましたが、ビットマップ結合インデックスは、他のソリューションよりも数桁速く実行されるようです。
サンプルデータ
--Create tables
create table customer
(
customer_id number,
region varchar2(100) not null
) nologging;
create table product
(
product_id number,
customer_id number not null,
category varchar2(100) not null
) nologging;
--Load 30M rows, 1M rows at a time. Takes about 6 minutes.
begin
for i in 1 .. 30 loop
insert /*+ append */ into customer
select (1000000*i)+level, 'Region '||trunc(dbms_random.value(1, 1000))
from dual connect by level <= 1000000;
commit;
insert /*+ append */ into product
select (1000000*i)+level, (1000000*i)+level
,'Category '||trunc(dbms_random.value(1, 1000))
from dual connect by level <= 1000000;
commit;
end loop;
end;
/
--Add primary keys and foreign key constraints.
alter table customer add constraint customer_pk primary key (customer_id);
alter table product add constraint product_pk primary key (product_id);
alter table product add constraint product_customer_fk
foreign key (customer_id) references customer(customer_id);
--Gather stats
begin
dbms_stats.gather_table_stats(user, 'CUSTOMER');
dbms_stats.gather_table_stats(user, 'PRODUCT');
end;
/
インデックスなし - 遅い
さすがに性能はイマイチ。このサンプル クエリは、私のマシンでは約 75 秒かかります。
SELECT count(*)
FROM product
JOIN customer ON product.CUSTOMER_ID = customer.customer_id
WHERE (product.CATEGORY = 'Category 1' AND customer.REGION = 'Region 1')
OR (product.CATEGORY = 'Category 2' AND customer.REGION = 'Region 2')
OR (product.CATEGORY = 'Category 888' AND customer.REGION = 'Region 888');
B ツリー インデックス - まだ遅い
計画は変更されますが、パフォーマンスは同じままです。これは、私の例が最悪の場合のインデックス作成シナリオであり、データが完全にランダムであるためであると考えられます。
create index customer_idx on customer(region);
create index product_idx on product(category);
begin
dbms_stats.gather_table_stats(user, 'CUSTOMER');
dbms_stats.gather_table_stats(user, 'PRODUCT');
end;
/
ビットマップ インデックス - 少し改善
これにより、パフォーマンスがわずかに向上し、約 61 秒になります。
drop index customer_idx;
drop index product_idx;
create bitmap index customer_bidx on customer(region);
create bitmap index product_bidx on product(category);
begin
dbms_stats.gather_table_stats(user, 'CUSTOMER');
dbms_stats.gather_table_stats(user, 'PRODUCT');
end;
/
ビットマップ結合インデックス - 信じられないほど高速
これで、クエリはほぼ瞬時に結果を返します。私の IDE はそれを 0 秒としてカウントします。
drop index customer_idx;
drop index product_idx;
create bitmap index customer_product_bjix
on product(product.category, customer.region)
FROM product, customer
where product.CUSTOMER_ID = customer.customer_id;
begin
dbms_stats.gather_table_stats(user, 'CUSTOMER');
dbms_stats.gather_table_stats(user, 'PRODUCT');
end;
/
インデックス コスト
ビットマップ結合インデックスは、B ツリーまたはビットマップ インデックスよりも作成に時間がかかります。B ツリー インデックスは、ビットマップまたはビットマップ結合インデックスに比べて非常に大きくなります。
select segment_name, bytes/1024/1024 MB
from dba_segments
where segment_name in ('CUSTOMER_IDX', 'PRODUCT_IDX'
,'CUSTOMER_BIDX', 'PRODUCT_BIDX', 'CUSTOMER_PRODUCT_BJIX');
SEGMENT_NAME MB
------------ --
CUSTOMER_IDX 726
PRODUCT_IDX 792
CUSTOMER_BIDX 88
PRODUCT_BIDX 96
CUSTOMER_PRODUCT_BJIX 184
クエリ スタイル
これはパフォーマンスには影響しませんが、次のようにクエリを縮小できる場合があります。
SELECT count(*)
FROM product
JOIN customer ON product.CUSTOMER_ID = customer.customer_id
WHERE (product.category, customer.region)
in (('Category 1', 'Region 1'),
('Category 2', 'Region 2'),
('Category 888', 'Region 888'));