このドキュメントでは、OBKV-Tableのデータモデルについて説明します。OBKV-Tableはテーブルモデルインターフェースを提供しており、概念定義がSQLに近く、行と列の概念も備えています。
テーブルの編成方法
テーブルの編成方法は、OBKV-Tableの基盤となるストレージ構造であり、データがディスク上に格納される方式を決定します。OBKV-Tableでは、以下の2種類のテーブル編成方法をサポートしています:
- インデックス組織テーブル (Index Organized Table)
- ヒープ組織テーブル (Heap Organized Table)。OBKVクライアントはV2.1.1バージョンからヒープ組織テーブルをサポートしています。
テーブルの編成方法は、CREATE TABLEステートメントでテーブルオプションORGANIZATIONを指定して設定します。
CREATE TABLE table_name
column_definition
ORGANIZATION [=] {INDEX | HEAP};
ここで、ORGANIZATION パラメータは、テーブル内のデータ行の格納順序を指定するために使用されます。取り得る値は以下の通りです:
- INDEX:テーブルモデルがクラスタ化インデックステーブルモデル、すなわちインデックス組織テーブルであることを示します。
- HEAP:テーブルモデルが非クラスタ化インデックステーブルモデル、すなわちヒープ組織テーブルであることを示します。
ORGANIZATION オプションを指定しない場合、そのオプションの値はパラメータ default_table_organization の値と同じになります。
インデックス組織テーブル
インデックス組織テーブルはOBKV-Tableのデフォルトのテーブル編成方法であり、ほとんどのシナリオに適しています。インデックス組織テーブルのインデックスは主キーに基づいているため、データを迅速に特定できます。
例えば、インデックス組織テーブルを作成します:
CREATE TABLE IF NOT EXISTS `employees` (
`employeeId` int NOT NULL,
`name` varchar(100) NOT NULL,
`department` varchar(50),
`position` varchar(50),
`workYear` int,
PRIMARY KEY (`employeeId`)
);
ヒープ組織テーブル
ヒープ組織テーブルは、書き込み性能とストレージ効率を重視する用途、特にデータをパーティションに分けて格納する用途に適しています。書き込み時に主キーの重複を確認する必要がないため、書き込み性能を高め、余分なI/Oを削減できます。また、データを主キー順に並べる必要がないため、マイナーコンパクションとメジャーコンパクション時のI/Oリソース消費も抑えられます。LOBなど、書き込みスループットとストレージコストが重要な用途に適しています。
例えば、ヒープ組織テーブルを作成します:
CREATE TABLE test (
c1 bigint,
c2 varchar(20),
c3 bigint,
UNIQUE INDEX idx_unique_c1 (c1) -- ユニークインデックスの作成はクエリを高速化できます
) ORGANIZATION = HEAP PARTITION BY KEY(c1) PARTITIONS 97; -- ORGANIZATIONパラメータでヒープテーブルを指定
利用制限は以下の通りです:
- ヒープ組織テーブルのローカル一意インデックスキー (Unique Key Local) またはヒープ組織テーブルの主キーテーブルの主キー (Primary Key) は、すべてのパーティションキー (Partition Key) を含む必要があります。
- ヒープ組織テーブルでインデックスを作成する際、インデックス名は主キーの最初の列名と同じにすることはできません。
- OceanBaseデータベースのヒープ組織テーブルは、現在以下のDDL操作をサポートしていません:
- 主キー制約操作
- 列を主キーに変更する
- 主キー列の型の長さを短くする
- 主キー列を追加する
- 主キーを自動インクリメント列に変更する
- 主キー列を削除する
- パーティションの分割
- ヒープ組織テーブルでサポートされていないインターフェースの詳細については、OBKV-Table操作タイプを参照してください。
テーブル識別フラグ
注意
OBKV-TableはV4.4.1からこの機能をサポートしています。システムがOBKVテーブルタイプを正確に認識できるように、テーブル作成時にkv_attributes設定を追加することを強く推奨します。
OBKV-Tableを作成する際には、kv_attributes='{"Table": {}}' 設定を追加して、OBKV-Tableとして正しく認識されるようにすることを推奨します。例:
CREATE TABLE `t1` (
`c1` bigint NOT NULL,
`c1sk` varchar(20) NOT NULL,
`c2` varbinary(1024) DEFAULT NULL,
`c3` varchar(20) DEFAULT NULL,
`c4` bigint DEFAULT NULL,
PRIMARY KEY(`c1`, `c1sk`))
-- '{"Table": {}}' この形式は変更できません。Tableは大文字小文字を区別しません。
kv_attributes = '{"Table": {}}'
partition by range columns (`c1`) (
PARTITION p0 VALUES LESS THAN (300),
PARTITION p1 VALUES LESS THAN (1000),
PARTITION p2 VALUES LESS THAN MAXVALUE);
テーブル構造
テーブル構造は、データがテーブル内で格納される方法を決定します。OBKV-Tableのテーブル構造は、主に以下の部分で構成されています:
- ColumnValue
- Row
ColumnValue
ColumnValueは実際には一対のキーと値、つまり 列名 + その列の値 の組み合わせを表します。主に初期代入やColumnValueオブジェクトの値の取得に使用されます。
ColumnValue empCol = new ColumnValue("department", "oceanbase_product");
Row
Rowは、1つ以上のColumnValueから構成される、1行分のデータを表すオブジェクトです。
以下に、1行分のデータを構築する例を示します。OBKV-Tableでは、記述を簡潔にするために複数の方法を提供しています。
方法1:
Row emp = row().add(colVal("employeeId", 12345))
.add(colVal("name", "zhangsan"))
.add(colVal("department", "oceanbase_dev"))
.add(colVal("position", "engineer"));
.add(colVal("workYear", 5));
方法2:
ColumnValue id = colVal("employeeId", 12345);
ColumnValue name = colVal("name", "zhangsan");
ColumnValue dep = colVal("department", "oceanbase_dev");
ColumnValue pos = colVal("position", "engineer");
ColumnValue workYear = colVal("workYear", 5);
Row emp = new Row();
emp.add(id, name, dep, pos, workYear);
説明
OBKV-Tableは、MutationFactoryにおいてColumnValue colVal(String columnName, Object value)やRow row(ColumnValue... columnValue)などの静的メソッドを提供するなど、インターフェースレベルでのシンタクティックシュガーを備えています。これにより、Rowのような複合オブジェクトの構築時にコーディング作業を軽減できます。
Rowkey:
- 主キーはOBKV-Tableにおいて非常に重要であり、各レコードの一意性を保証します。
- 主キーが異なるフィールドで構成されている場合、純粋なKVデータベースではユーザーが異なるフィールドをまとめてエンコードする必要がありますが、OBKV-Tableではユーザーが直接フィールドに基づいてRowkeyを入力できます。
クエリ
OBKV-Tableのクエリ方法は、主に以下の部分で構成されています:
- ScanRange
- Filter
ScanRange
ScanRangeは通常、主キーまたは主キーのプレフィックスであり、定義インターフェースは以下のとおりです:
TableQuery addScanRange(Object[] starts, Object[] ends);
ScanRange
ScanRangeは通常、主キーまたは主キーのプレフィックスであり、定義インターフェースは以下のとおりです:
TableQuery addScanRange(Object[] starts, Object[] ends);
パラメータ説明:
- 開始値と終了値をそれぞれ配列で指定します。各配列の要素数は主キー列の数と同じです。
<starts[m], ends[m]>は主キーのm列目の値の範囲を表します。
サンプルスキーマは以下のとおりです:
CREATE TABLE test(c1 INT, c2 INT, c3 INT, c4 INT, PRIMARY KEY(c1, c2));
サンプルクエリは以下のとおりです:
//SELECT c1, c2 FROM test WHERE c1 = 0 AND c2 >=2 AND c2 <= 40;
QueryResultSet resultSet = obTableClient.query("test")
.select("c1","c2")
.setScanRangeColumn('c1', "c2")
.addScanRange(new Object[]{0, 2}, new Object[]{0, 40})
.execute();
FilterとScanRangeの違い
SQLとOBKV-Tableは、クエリ条件の処理方法が異なります:
SQL:
WHERE句では、すべての列をフィルタ条件として使用できます。- 主キーかどうかを区別する必要はありません。
- SQLオプティマイザーは、主キー関連の条件を自動的にScanRangeに変換し、非主キー条件をFilterに変換します。
OBKV-Table:
- 主にシンプルなトランザクション処理シナリオで使用されます。
- 組み込みのオプティマイザーはありません。
- ユーザーがインターフェースを通じてScanRangeとFilterを設定する必要があります。
なぜScanRangeとFilterを区別する必要があるのでしょうか?
ScanRange(スキャン範囲):
- 主キーまたは主キープレフィックスに使用されます。
- データベース内のデータは主キーでソートされています。
- 条件に合致するデータ範囲を迅速に特定できます。
Filter(フィルタ):
- 非主キー列に使用されます。
- データを直接特定することはできず、スキャンが必要です。
- スキャン中にデータが条件に合致するかどうかを判断するために使用されます。
両方が存在する場合、クエリプロセスは以下のとおりです:
- まず、ScanRangeを使用して、条件に合致する可能性のあるデータ範囲を迅速に特定します。
- その後、この範囲内でFilterを使用して、すべての条件を満たす最終的なデータをさらに絞り込みます。
注意点:
- ScanRangeはクエリ計画に必ず含まれている必要があります。そうでない場合、Filterがあっても全表スキャンのパスを実行し、データベースの大量のI/Oリソースを消費することになります。
- OBKV-Tableサーバー側にはオプティマイザー機能が提供されていないため、Filterに記述された主キー条件をScanRangeに変換することはありません。