OceanBaseにおけるグローバルインデックスとローカルインデックスの管理

1. 主キーとセカンダリインデックス

  • メインテーブル:CREATE TABLE文で作成されたテーブルオブジェクト。インデックスオブジェクトが依存するテーブル(CREATE INDEX文のON句で指定されるテーブル)。
  • 主キー:OceanBaseの各テーブルには主キーがあり、内部的には主キーによってデータが整理されている。ユーザーが明示的に主キーを指定しない場合、システムは隠し主キーを自動生成するが、これはクエリでは参照できない。
  • インデックス(インデックステーブル):CREATE INDEX文で作成されるインデックスオブジェクト。理解を助けるために、インデックスをテーブルオブジェクトとして扱うこともしばしばある。

実際のデータベーステーブルを例に説明する。このテーブル名はemployee(従業員情報テーブル)であり、構造は以下の通り:

employee
{
emp_id, /* 従業員ID */
emp_name /* 従業員名 */
dept_id, /* 部門ID */
...
}

2. 伝統的な「非パーティションテーブル」における主テーブルとインデックスの関係

従来の「非」パーティションテーブルにおいて、主テーブルとインデックスの対応関係は以下の通り:

  • 主テーブルのすべてのデータは一つの完全なデータ構造に格納され、主テーブル上の各インデックスもまた一つの完全なデータ構造(たとえば一般的なB+木)に対応する。
  • 主テーブルのデータ構造とインデックスのデータ構造は1対1の関係を持つ。

employeeテーブルにおいて、emp_idを基準としたインデックスidx_emp_idが存在する。

3. ローカルインデックスとグローバルインデックス

パーティションテーブルが導入されたことで状況は変化した。主テーブルのデータはパーティションキーの値に基づいて複数のパーティションに分割され、それぞれのパーティションは独立したデータ構造を持ち、パーティション間のデータは重複しない。このため、インデックスが依存する単一のデータ構造は存在しなくなった。この問題に対処するために「ローカルインデックス」と「グローバルインデックス」の概念が導入された。

  • ローカルインデックス:別名はパーティションインデックス。インデックス作成時のパーティションキーワードはLOCALであり、パーティションキーはテーブルのパーティションキーと一致し、パーティション数もテーブルのパーティション数と一致する。つまり、ローカルインデックスのパーティションメカニズムはテーブルのパーティションメカニズムと同じである。
  • グローバルインデックス:グローバルキーワードをインデックス属性に指定することで作成される。ローカルインデックスと比較して、グローバルインデックスの最大の特徴は、そのパーティションルールがテーブルのパーティションルールとは独立していること。グローバルインデックスは独自のパーティションルールとパーティション数を定義でき、テーブルのパーティションルールと一致する必要はない。

4. ローカルインデックス

パーティションテーブルのローカルインデックスは非パーティションテーブルのインデックスと似ており、インデックスのデータ構造は主テーブルのデータ構造と1対1の関係を保つ。ただし、主テーブルがパーティション化されているため、各パーティションには個別のインデックスデータ構造が存在する。

5. グローバルインデックス - グローバル非パーティションインデックス

パーティションテーブルのグローバルインデックスは主テーブルのパーティションとの1対1の関係を維持せず、すべての主テーブルパーティションのデータを一つの全体としてグローバルインデックスを構築する。さらに、グローバルインデックスは独自のデータ分布パターンを定義できる。つまり、非パーティションモードまたはパーティションモードを選択できる:

  • グローバル非パーティションインデックス(Global Non-Partitioned Index)
  • グローバルパーティションインデックス(Global Partitioned Index)

6. グローバルインデックス - グローバルパーティションインデックス

上記と同様、パーティションテーブルのグローバルインデックスは主テーブルのパーティションとの1対1の関係を維持せず、すべての主テーブルパーティションのデータを一つの全体としてグローバルインデックスを構築する。さらに、グローバルインデックスは独自のデータ分布パターンを定義できる。

7. 機能要件:パーティションキーに関係しないフィールドに一意インデックスを設定

ローカルインデックスにおいて、「インデックスキーが主テーブルのすべてのパーティションキーを含まない場合」、そのインデックスキーに対応するデータはすべてのパーティションに存在する可能性がある。例えば、employeeテーブルがemp_idでパーティション化されているが、emp_nameについての一意制約をローカルインデックスで実現することは不可能である。なぜなら、あるインデックスキーの値がすべてのパーティションのローカルインデックスに存在する可能性があり、スキャン時にすべてのパーティションを走査する必要があるため、効率が悪く、CPUおよびIOリソースの無駄遣いを引き起こすからである。

8. グローバル非パーティションインデックスとグローバルパーティションインデックスの比較

  • グローバルインデックスのパーティションキーは常にインデックスキーの接頭辞である必要がある。
  • グローバル非パーティションインデックス:この場合、インデックス構造は「非パーティション」テーブルと変わらない。一つの完全なインデックスツリーのみが存在し、一意性が保証される。また、複数のパーティションスキャンの問題もない。
  • グローバルパーティションインデックス:データは特定のインデックスパーティションにのみ存在するため、各インデックスパーティション内で一意性制約を満たせば、テーブル全体で一意性が保証される。グローバルインデックスは特定のインデックスキーのデータが特定のインデックスパーティションにのみ存在することを保証するため、単一のキー値に対するインデックススキャンやキー範囲に対するスキャンでも、必要なパーティションを直接特定できる。

9. ローカルインデックスとグローバルインデックスの実行計画の比較

ローカルインデックス

create table t_p_hash (c1 varchar(20),c2 int, c3 varchar(20)) partition by hash(c2) partitions 3;
create index idx_t_p_hash_c1 on t_p_hash (c1) local;
create index idx_t_p_hash_c3 on t_p_hash (c3) local;
explain extended select c1,c2 from t_p_hash where c3='100';

出力例:

|ID|OPERATOR |NAME |EST. ROWS|COST |
----------------------------------------------------------------------
|0 |PX COORDINATOR         |         |2970 |19207|
|1 | EXCHANGE OUT DISTR    |:EX10000 |2970 |18364|
|2 |  PX PARTITION ITERATOR|         |2970 |18364|
|3 |   TABLE SCAN          |t_p_hash(idx_t_p_hash_c3)|2970 |18364|
======================================================================
Outputs & filters: 
-------------------------------------
0 - output([t_p_hash.c1(0x7f8b99cb4370)], [t_p_hash.c2(0x7f8b99cb30a0)]), filter(nil)
1 - output([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), filter(nil), dop=1
2 - output([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), filter(nil)
3 - output([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), filter(nil), 
access([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), partitions(p[0-2]), 
is_index_back=true, range_key([t_p_hash.c3(0x7f8b99cb3e50)], [t_p_hash.c2(0x7f8b99cb30a0)],
[t_p_hash.__pk_increment(0x7f8b99cd9660)]), range(100,MIN,MIN ; 100,MAX,MAX), 
range_cond([t_p_hash.c3(0x7f8b99cb3e50) = '100'(0x7f8b99cb37f0)])

インデックスのバックフィルはtable scan演算子の中に含まれる:

is_index_back=true
TABLE SCAN
t_p_hash(idx_t_p_hash_c3)

c3='100'、partitions(p[0-2])

パーティションキーが指定されていない場合、ローカルインデックスはパーティションの選択を実行できない。パーティションキーはC2である。

グローバルインデックス

create table t_p_key (c1 varchar(20),c2 int,c3 varchar(20)) partition by key (c2) partitions 3;
create unique index idx_t_p_key_c3_g on t_p_key (c3) global partition by key (c3) partitions 3;
explain extended select c1,c2 from t_p_key where c3='66';

出力例:

|ID|OPERATOR |NAME |EST. ROWS|COST|
----------------------------------------------------------
|0 |TABLE LOOKUP|t_p_key                  |1 |91 |
|1 | TABLE SCAN |t_p_key(idx_t_p_key_c3_g)|1 |36 |
==========================================================
Outputs & filters: 
-------------------------------------
0 - output([t_p_key.c1(0x7f8b99cb6050)], [t_p_key.c2(0x7f8b99cb3a80)]), filter(nil), 
partitions(p[0-2])
1 - output([t_p_key.c2(0x7f8b99cb3a80)], [t_p_key.__pk_increment(0x7f8b99cd9660)]), filter(nil), 
access([t_p_key.c2(0x7f8b99cb3a80)], [t_p_key.__pk_increment(0x7f8b99cd9660)]), 
partitions(p2),
is_index_back=false,
range_key([t_p_key.c3(0x7f8b99cb5010)], [t_p_key.shadow_pk_0(0x7f8b99cda660)], 
[t_p_key.shadow_pk_1(0x7f8b99cda8f0)]), range(66,MIN,MIN ; 66,MAX,MAX), 
range_cond([t_p_key.c3(0x7f8b99cb5010) = '66'(0x7f8b99cb5760)])
  1. WHERE条件によりグローバルインデックスのパーティションp2を絞り込む:
    partitions(p2)
    where c3='66'
  2. グローバルインデックスのtable scan操作で主キーを取得:
    TABLE SCAN
    t_p_key(idx_t_p_key_c3_g)
  3. table lookup演算子を使って主テーブルを正確にパーティションスキャンし、全パーティションのスキャンを回避:
    TABLE LOOKUP

10. ローカルインデックスとグローバルインデックスの選択

  • 「完全なパーティションキーが含まれる」クエリ条件の場合、ローカルインデックスが最も効率的である。
  • 「完全なパーティションキーが含まれない」一意性制約が必要な場合:
    • グローバルインデックスを使用
    • または、ローカルインデックスを使用し、インデックス列にテーブルのパーティションキーを含める
  • その他のケースについては、個別に検討:
    • 通常、グローバルインデックスは高頻度かつ正確に一致するクエリ(例:単一行クエリ)において高速化とIO削減が可能。範囲クエリに対してはどちらが良いかはケースバイケース。
    • グローバルインデックスによるDML操作における追加オーバーヘッドにも注意が必要:データ更新時における分散トランザクションの発生、トランザクションの規模が大きいほど複雑になる。
  • データ量が多い、またはインデックスのホットスポットが発生しやすい場合は、グローバルパーティションインデックスの作成を検討。

タグ: OceanBase インデックス管理 ローカルインデックス グローバルインデックス パーティションテーブル

5月27日 16:42 投稿