データベースでは、インデックスを作成することでクエリのパフォーマンスを向上させることができます。データテーブルのデータ量が増えてくると、パーティショニングによってデータテーブルを複数のシャードに分割し、ロードバランシングを利用してデータシャードをクラスタ全体に分散させることで、クラスタ全体のサービス能力を向上させることができます。本記事では、主にローカルインデックス、グローバルインデックス、一意インデックスについて説明します。
インデックスタイプ
OceanBaseデータベースでは、主に以下のインデックスタイプがあります:
ローカルパーティションインデックス:インデックス作成時にキーワード
LOCALを指定したインデックスはローカルインデックスです。ローカルインデックスはパーティションルールを指定する必要がなく、そのパーティション属性は主表の属性と一致し、主表のパーティション操作に伴って変更されます。グローバルパーティションインデックス:インデックス作成時にキーワード
GLOBALを指定したインデックスはグローバルインデックスです。グローバルインデックスはパーティションルールに従ってテーブルを分割できます。一意インデックス:インデックスキー値が一意であることを示し、キーワード
UNIQUEで表します。プレフィックスインデックス:パーティションインデックステーブルのパーティションキーがインデックス列の左プレフィックスである場合、そのインデックスはプレフィックスインデックスと呼ばれます。これはMySQLの長文字列のプレフィックスインデックスとは異なります。例えば、インデックステーブル
idxがc1とc2列に基づいて作成されている場合、そのインデックステーブルのパーティションキーがc1であれば、そのインデックスはプレフィックスインデックスと呼ばれます。インデックステーブルのパーティションキーがc2または他の列である場合は、非プレフィックスインデックスと呼ばれます。非プレフィックスインデックス:パーティションプレフィックスインデックスに対して、パーティションインデックスがプレフィックスインデックスでない場合、それは非プレフィックスインデックスと呼ばれます。
ローカルインデックス
ローカルインデックスは、パーティションキーに基づいてローカルプレフィックスインデックスとローカル非プレフィックスインデックスに分類されます。
ローカルプレフィックスインデックス
パーティションキーがインデックスの左プレフィックスであり、かつインデックスにサブパーティションキーが含まれる場合、このインデックスはローカルプレフィックスインデックスです。ローカルプレフィックスインデックスは一意インデックスまたは非一意インデックスにすることができます。
インデックスキーを指定したクエリは、ローカルプレフィックスインデックスを利用して特定のインデックスパーティションを一意に特定できるため、結果セットが小さいがパーティションプルーニングが必要な場合に非常に適しています。
例えば、テーブルAにローカルインデックス idx(c1,c2,c3) が存在する場合、主表は c1 に基づいてパーティション化されます。ローカルインデックスの特性により、インデックステーブルと主表のパーティション方式は同じであるため、idx も c1 をパーティションキーとします。定義からわかるように、これはローカルプレフィックスインデックスです。インデックスキーを指定したクエリを処理する際、パーティションキー c1 の値によって唯一のインデックスパーティションを特定できるため、インデックスパーティションへのアクセスを大幅に削減できます。
ローカル非プレフィックスインデックス
インデックステーブルがローカルプレフィックスインデックスでない場合、それはローカル非プレフィックスインデックスです。可能性のある状況としては、インデックステーブルのパーティションキーがインデックスの左プレフィックスではない場合、またはインデックスにサブパーティションキーが含まれていない場合があります。
パーティションキーがインデックスの部分集合でない場合、ローカル非プレフィックスインデックスは一意インデックスにすることはできません。 インデックスキーを指定したクエリは、ローカル非プレフィックスインデックスを利用してインデックスパーティションを特定できず、すべてのインデックスパーティションにアクセスする必要があるため、データ量が多く並行処理を重視するシナリオに適しています。
例えば、テーブルAにローカルインデックス idx(c1,c2,c3) が存在する場合、主表は c4 列に基づいてパーティション化されます。定義からわかるように、これはローカル非プレフィックスインデックスです。ユーザーがインデックスキーを指定してクエリを実行すると、パーティションキーによってインデックスパーティションを特定できないため、結果を取得するためにはすべてのインデックスパーティションにアクセスする必要があります。このようなシナリオでは、並行実行が重要な役割を果たすことができます。
グローバルインデックス
グローバルインデックスは、主表とは独立した独自のパーティション定義を持ちます。同時に、グローバルインデックステーブルのパーティションも分割およびメジャーコンパクションが可能です。 一般的に、主表とグローバルインデックスのパーティショニング方式が完全に同じ場合、一意性を持つ非プレフィックスインデックスを除き、他のインデックスはローカルインデックスとして定義することを推奨します。グローバルインデックスは、パーティションの管理とメンテナンスにおいてローカルインデックスよりもはるかに高いコストがかかります。また、クエリコストやパーティションプルーニングの観点から見ると、同じパーティショニング方式を持つグローバルインデックスとローカルインデックスの効果は同じです。
グローバルプレフィックスインデックス
グローバルパーティションインデックスの最初のフィールドがテーブルの主パーティションフィールドである場合、そのインデックスはグローバルプレフィックスインデックスです。
グローバルプレフィックスインデックスは、一意インデックスまたは非一意インデックスにすることができます。
グローバルプレフィックスインデックスは、Rangeパーティションでのみ意味をなし、Hashパーティションインデックスには意味がありません。その理由は、ユーザーがHashパーティションインデックスを選択した場合、ユーザーのクエリパターンは必ずインデックスキーによるポイントクエリであるため、インデックスキーがパーティションキーをカバーしている場合、プレフィックスインデックスであるかどうかは意味がなく、ユーザーが指定したインデックスキー値からインデックスパーティションを計算できるからです。ユーザーがすべてのパーティションキー値を指定しない場合、Hashパーティションインデックスはすべてのパーティションデータにアクセスする必要がありますが、Rangeパーティションではある程度のパーティションプルーニングが可能です。
グローバル非プレフィックスインデックス
OceanBaseデータベースはグローバル非プレフィックスインデックスをサポートしていません。グローバル非プレフィックスインデックスは、クエリ最適化にそれほど意味がありません。
例えば、テーブルAにグローバルインデックスidx(c1,c2)があり、idxがc2でパーティション化されている場合、idxはグローバル非プレフィックスインデックスです。この場合、ユーザーがすべてのインデックスキー値を指定した場合にのみパーティションプルーニングが可能であり、それ以外の場合はすべてのインデックスパーティションをスキャンする必要があるため、ユーザーはc1キーを使って直接パーティション化し、c1を使ってプレフィックスフィルタリングによるパーティションプルーニングを行う方が理にかなっています。
一意インデックス
一意インデックスは、グローバルインデックスまたはローカルインデックスとして定義できます。
一意インデックスをローカルインデックスとして定義する場合、インデックスキーがインデックスパーティションキーをカバーしているという一定の条件を満たす必要があります。 例えば、テーブルAに一意インデックスidx(c1,c2,c3)があり、インデックステーブルidxが(c1,c2)でパーティション化されている場合、同じ(c1,c2)は必ず同一のパーティションに入ることが保証されるため、単一のパーティション内で一意性を維持するだけで済みます。インデックステーブルidxが(c2,c4)でパーティション化されている場合、インデックスがパーティションキーをカバーしていないため、ローカルインデックスではパーティション間のインデックスキーの一意性を保証できず、このようなローカル一意インデックスは作成できません。
インデックス作成戦略
インデックスを作成する際には、ユーザーのクエリパターン、インデックス管理、パフォーマンス、可用性などの要件を総合的に考慮し、自社のビジネスに最適なインデックス方式を選択する必要があります。
ユーザーが一意インデックスを必要とし、かつインデックスキーがすべてのパーティションキーをカバーしている場合は、ローカルインデックスとして定義できます。そうでない場合は、グローバルインデックスを使用する必要があります。
主表のパーティションキーがインデックスの部分集合である場合は、ローカルインデックスを使用できます。
主表のパーティション属性とインデックスのパーティション属性が同じ場合は、ローカルインデックスを使用することを推奨します。
ユーザーがインデックスパーティション管理のコストを重視し、主表のパーティションが常に削減され続ける場合は、グローバルインデックスの作成を避けることが望ましいです。これは、主表のパーティション削減操作によりグローバルインデックスの変更が大きくなり、回復が困難になる可能性があり、さらにインデックスが使用不能になる可能性があるためです。
ユーザーのクエリが常にすべてのインデックスパーティションキー値を指定する場合は、パーティションキーを含めずに他の列にインデックスを作成するだけで済み、メンテナンスコストとストレージコストを削減できます。パーティションキーを指定しないクエリについては、プレフィックスインデックスの方がパーティションプルーニングやデータ量が比較的小さいシナリオに適しており、非プレフィックスインデックスは一般的にパーティション並列処理やデータ量が比較的大きいシナリオに適しています。
パーティション選択戦略
インデックスパーティションを作成する際、単一のインデックステーブルのデータ量が大きすぎる場合は、パーティション化を行い、クエリのパーティション並列処理とロードバランシングを改善する必要があります。
ユーザーのクエリが指定インデックスキーによるポイントクエリが多い場合、アクセスするパーティション数とアクセスの並行性の観点から見ると、HashパーティションとRangeパーティションのクエリコストは大差ありません。しかし、データにホットスポットが存在する場合、Hashパーティションの方がホットスポット問題を回避できます。
ユーザーのクエリが指定インデックスキーによる範囲クエリが多い場合、アクセスするパーティション数の観点から見ると、Rangeパーティションの方がHashパーティションより優れています。しかし、並行性の観点から見ると、Hashパーティションの方が並行クエリの利点をより有効に活用でき、クエリ結果セットが非常に大きい場合、Hashパーティションの並行クエリの方が大きなパフォーマンス優位性を発揮できるはずです。
クエリ時のインデックス選択戦略
ユーザーがパーティションプルーニングを重視する場合は、プレフィックスインデックスを選択する方が良いです。クエリのフィルタリング条件でパーティションプレフィックスの値を指定することで、パーティションプルーニングを最大限に行い、インデックスパーティションデータの読み取りを削減できます。
ユーザーがスループットを重視し、アクセスするデータ量が比較的大きい場合は、非プレフィックスインデックスを選択する方がパフォーマンスを向上させることができます。パーティション並列処理により、パーティションキーに対する範囲クエリを解決できます。ローカル非プレフィックスインデックスはすべてのパーティションに並行アクセスできますが、ローカルプレフィックスインデックスはパーティションプルーニングを行い、少数のパーティションで大量のデータセットを処理するため、応答時間は並行クエリよりも若干悪くなる可能性があります。