本記事では、OceanBaseにおけるカラムストアテーブル、カラムストアインデックス、および行ストアテーブルや行ストアインデックスとの組み合わせ方を紹介します。また、決済明細シナリオを例に、具体的なテーブル作成、インデックス作成、クエリの実装例を示し、選定結果を具体的なSQLに落とし込む方法を説明します。
カラムストアテーブルとカラムストアインデックスの概念
カラムストアテーブル:主テーブルのベースラインをカラムストア形式で保存します。作成方法は
WITH COLUMN GROUP(each column)(純粋なカラムストア)またはWITH COLUMN GROUP(all columns, each column)(行と列のハイブリッドストア)です。詳細については、カラムストアの概要 — カラムストアテーブルと行と列のハイブリッドテーブルの作成方法を参照してください。カラムストアインデックス:インデックステーブル自体をカラムストア形式で保存するもので、「カラムストアテーブルに特別なインデックスタイプを追加する」というものではありません。インデックス作成時に
WITH COLUMN GROUP(each column)を指定してカラムストアインデックスを作成します。インデックステーブルでも行ストアまたはカラムストアの列グループを指定できます。行ストアインデックス:インデックステーブルは行ストア形式で編成されます(デフォルトまたは
WITH COLUMN GROUP(all columns))。ポイントクエリやカバーインデックスによるテーブルへの再アクセスパスに適しています。
以下の例はすべて、同じ業務ワイドテーブル payment_ledger(決済明細) を中心に説明します。フィールドには order_id、pay_time、pay_channel、txn_status、amount などが含まれており、異なる組み合わせにおける読み書き特性を比較しやすくなっています。
シナリオの組み合わせ
シナリオ |
適用業務 |
戦略 |
メリット |
デメリット |
|---|---|---|---|---|
| 行ストアテーブル + カラムストアインデックス | トランザクション型業務が主体でありながら、一定の分析型クエリの要件があり、かつテーブルが大規模なワイドテーブルの場合。 | カラムストアインデックスを作成して分析系クエリのパフォーマンスを向上させる。 | 一部の列に対してのみ冗長ストレージを行い、それもカラムストア形式である。 | クエリステートメントに応じて適切なフィールドを特定し、カラムストアインデックスを作成する必要があり、データ書き込み時にはインデックステーブルのデータをメンテナンスする必要があるため、書き込みパフォーマンスは相対的にやや低下する。 |
| カラムストアテーブル + 行ストアインデックス | 分析型業務が主体であるが、効率的な単純クエリ(ポイントクエリなど)のサポートも必要な場合。 | インデックス作成時にテーブルへの再アクセスを回避するため、カバーインデックスを作成する。 | 一部の列データのみを冗長的に保存する。現在、カラムストアテーブルから行ストアテーブルや行と列の冗長テーブルへのオンライン変換はサポートされていないため、行ストアインデックスを追加することでクエリの最適化を図ることができる。 | クエリステートメントに応じて適切なフィールドを特定し、行ストアインデックスを作成する必要があり、データ書き込み時にはインデックステーブルのデータをメンテナンスする必要があるため、書き込みパフォーマンスは相対的にやや低下する。 |
| 行と列のハイブリッドテーブル | トランザクション型と分析型の両方のクエリを同時に扱う必要があり、かつクエリのパターンが多様な場合。 | ベースラインデータを直接2つ冗長的に保存し、システムがクエリの特性に応じて適切な実行計画を生成する。クエリロジックは、デフォルトで範囲スキャンはカラムストアモードで行われ、ポイントクエリは行ストアモードにフォールバックする。 | 業務のクエリ特性を分析する必要がなく、分析系クエリの結果は強整合性を持つ。 | より多くのディスク容量を消費し、実行計画が不正確になりやすい傾向がある。 |
| 純粋なカラムストアテーブル(インデックスなし) | APシナリオ | WITH COLUMN GROUP(each column) を使用してカラムストアテーブルを作成し、データを列指向で保存する |
大量データのインポートと、列数が少ないワイドテーブルのスキャン効率が高い | 主キー/注文番号によるポイントクエリが遅い |
例
例1:行ストアテーブル + カラムストアインデックス
決済のコアパスは INSERT と order_id による状態の照会です。運用レポートでは、チャネルや日付ごとに amount の SUM/COUNT を行います。テーブルの列数は多いですが、分析では少数の列のみをスキャンします。
テーブルとカラムストアインデックスの作成:
CREATE TABLE payment_ledger (
txn_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
pay_time DATETIME NOT NULL,
pay_channel VARCHAR(32) NOT NULL,
txn_status TINYINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
merchant_id BIGINT NOT NULL,
remark VARCHAR(256) DEFAULT NULL,
PRIMARY KEY (order_id, pay_time, txn_id)
)
PARTITION BY HASH(order_id) PARTITIONS 64
WITH COLUMN GROUP(all columns);
-- カラムストアインデックス:分析でよく使用される列の冗長化により、ワイドテーブルの全列スキャンを回避する
CREATE INDEX idx_pay_analytics
ON payment_ledger (pay_time, pay_channel, txn_status, amount)
WITH COLUMN GROUP(each column);
典型的なTPクエリ(メインテーブルの行ストア + 主キー):
SELECT txn_id, txn_status, amount, pay_channel
FROM payment_ledger
WHERE order_id = 10086001
ORDER BY pay_time DESC
LIMIT 1;
典型的な分析クエリ(オプティマイザーはカラムストアインデックスを選択し、インデックス内の列のみをスキャンする場合があります):
SELECT pay_channel,
SUM(amount) AS succ_amt,
COUNT(*) AS succ_cnt
FROM payment_ledger
WHERE pay_time >= '2025-01-01'
AND pay_time < '2025-02-01'
AND txn_status = 1
GROUP BY pay_channel;
メインテーブルは行ストアを維持し、高並行性の書き込みとポイントクエリに影響を与えません。分析負荷はカラムストアインデックスが引き受けるため、テーブル全体を純粋なカラムストアに変更するよりも、TPに対してより安全です。インデックス列はフィルタリングと集計に必要なフィールドをカバーしている必要があり、メインテーブルへの戻りスキャンやワイド行のスキャンを避けるためです。
例2:カラムストアテーブル + 行ストアインデックス(分析を主とし、ポイントクエリを補う)
取引明細は既にバッチインポート/照合が主であり、日常的なSQLはほとんどが全パーティションのスキャンと集計です。カスタマーサポートやリスク管理などのシナリオでは、order_id に基づいて個別の注文を照会します。
テーブル作成と行ストアのオーバーライディングインデックス:
CREATE TABLE payment_ledger_cs (
txn_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
pay_time DATETIME NOT NULL,
pay_channel VARCHAR(32) NOT NULL,
txn_status TINYINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
merchant_id BIGINT NOT NULL,
PRIMARY KEY (txn_id, pay_time)
)
PARTITION BY RANGE COLUMNS(pay_time) (
PARTITION p202501 VALUES LESS THAN ('2025-02-01 00:00:00'),
PARTITION p202502 VALUES LESS THAN ('2025-03-01 00:00:00')
)
WITH COLUMN GROUP(all columns, each column);
-- 行ストアインデックス + STORING:注文のポイントクエリ時にメインテーブルのワイド行への戻りを回避する
CREATE INDEX idx_order_lookup
ON payment_ledger_cs (order_id)
STORING (txn_status, amount, pay_time, pay_channel)
WITH COLUMN GROUP(all columns);
分析クエリ(メインテーブルのカラムストア):
SELECT merchant_id, SUM(amount) AS total
FROM payment_ledger_cs
WHERE pay_time >= '2025-01-01' AND txn_status = 1
GROUP BY merchant_id;
ポイントクエリ(行ストアインデックス):
SELECT txn_status, amount, pay_time, pay_channel
FROM payment_ledger_cs
WHERE order_id = 10086001
ORDER BY pay_time DESC
LIMIT 5;
主表 each column は集計系SQLの列ストアスキャンの利点を保証し、ポイントクエリは行ストアインデックスが担当します。
例3:純粋な列ストアテーブル(広範囲分析、TP値要求緩め)
このテーブルは、日次決済照合や監督機関への報告などのバッチ処理のみに使用され、オンラインでの注文番号によるクエリは発生しません。また、インポート性能を優先するため、主キーを設定しないことも許容されます。詳細については、APシナリオにおける主キー設計の実践を参照してください。
CREATE TABLE payment_ledger.dw (
txn_id BIGINT,
order_id BIGINT,
pay_time DATETIME,
pay_channel VARCHAR(32),
txn_status TINYINT,
amount DECIMAL(18,2),
merchant_id BIGINT
)
WITH COLUMN GROUP(each column);
-- バッチインポート後は、メジャーコンパクションと統計情報の収集を推奨します
SELECT DATE(pay_time) AS dt,
COUNT(*) AS txn_cnt,
SUM(CASE WHEN txn_status = 1 THEN amount ELSE 0 END) AS succ_amt
FROM payment_ledger_dw
WHERE pay_time >= '2025-01-01' AND pay_time < '2025-02-01'
GROUP BY DATE(pay_time);
例4:行と列のハイブリッドテーブル
業務上、一つの payment_ledger テーブルで支払いクエリと運用分析の両方を扱う必要があり、二重のベースラインストレージと高いメジャーコンパクションコストを許容します。
CREATE TABLE payment_ledger_htap (
txn_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
pay_time DATETIME NOT NULL,
pay_channel VARCHAR(32) NOT NULL,
txn_status TINYINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
merchant_id BIGINT NOT NULL,
PRIMARY KEY (order_id, pay_time, txn_id)
)
PARTITION BY HASH(order_id) PARTITIONS 64
WITH COLUMN GROUP(all columns, each column);
-- ポイントクエリ(オプティマイザーは行ストア経路を優先)
SELECT amount, txn_status FROM payment_ledger_htap
WHERE order_id = 10086001 AND txn_status = 1
ORDER BY pay_time DESC LIMIT 1;
-- 分析クエリ(オプティマイザーは列ストア経路を優先)
SELECT pay_channel, SUM(amount) FROM payment_ledger_htap
WHERE pay_time >= '2025-01-15' AND txn_status = 1
GROUP BY pay_channel;