極端なシナリオでは、単一クエリで読み取る行数は多くないものの、そのクエリのQPSが非常に高く、結果として膨大なデータ読み取りが発生する場合があります。マシンのI/O能力が限られている場合、ディスクI/Oがパンクする可能性があります。
以下のシナリオを例にします:
CREATE TABLE T1 (C1 INT, C2 INT, C3 INT, C4 INT, ... ,C16 INT);
CREATE INDEX IDX_C1 ON T1 (C1);
EXPLAIN SELECT C3, C4 FROM T1 WHERE C1 = 1;
| =========================================
|ID|OPERATOR |NAME |EST. ROWS|COST|
-----------------------------------------
|0 |TABLE SCAN|T1(IDX_C1)|4 |229 |
=========================================
Outputs & filters:
-------------------------------------
0 - output([T1.C3], [T1.C4]), filter(nil),
access([T1.C3], [T1.C4]), partitions(p0)
上記のクエリは、C1列のインデックスを使用することで、最適なQuery Rangeを得ています。このクエリは、IDX_C1で毎回スキャンするデータ量が非常に少ないです。また、このクエリはC3、C4列の結果を取得する必要がありますが、IDX_C1にはこれら2列の値が保存されていないため、メインテーブルに対して再アクセス(ヒント)が必要です。クエリ実行ごとに、IDX_C1で少量の順次読み取りが発生し、メインテーブルでも少量のランダム読み取りが発生します。クエリのQPSが非常に高い場合、メインテーブルに対して大量のランダム読み取りが発生し、システムのI/Oリソースを大幅に消費します。このようなシナリオでは、メインテーブルへの再アクセスを回避するために、カバーインデックスを作成するという追加の最適化の方向性を検討する必要があります。上記のクエリを例にすると、カバーインデックスを作成して最適化できます。
CREATE INDEX IDX_C1_C3_C4 ON T1 (C1, C3, C4);
EXPLAIN SELECT C3, C4 FROM T1 WHERE C1 = 1;
| ===============================================
|ID|OPERATOR |NAME |EST. ROWS|COST|
-----------------------------------------------
|0 |TABLE SCAN|T1(IDX_C1_C3_C4)|4 |46 |
===============================================
Outputs & filters:
-------------------------------------
0 - output([T1.C3], [T1.C4]), filter(nil),
access([T1.C3], [T1.C4]), partitions(p0)
このクエリは、IDX_C1_C3_C4にアクセスする際、最適なQuery Rangeを持ち、メインテーブルへの再アクセスも不要です。実際のクエリ実行時には、インデックスに対して少量の順次スキャンのみが発生します。これにより、クエリによって生成されるI/Oを大幅に削減できます。
一般的に、カバーインデックスを作成する必要があるシナリオは、いくつかの特徴を満たす必要があります:
- データベースのI/O負荷が非常に高い
- 大量のI/Oは、数個の高頻度クエリがメインテーブルに再アクセスすることで発生するランダムI/Oによるものである
- 高頻度クエリが読み取る列はそれほど多くない
このような状況では、これらの高頻度クエリに対して、ターゲットを絞ったカバーインデックスを作成して最適化できます。カバーインデックスを利用してテーブルへの再アクセスを削減するシナリオは、比較的まれです。