プランキャッシュの欠点
前のセクションで説明したように、パラメータ化されたSQLに対して実行計画が生成されると、その計画はプランキャッシュに保存され、以降のSQL実行ではこの実行計画が再利用されます。このような計画の再利用は計画生成時間を節約できますが、データに偏りがある場合には問題が生じることがあります。以下に例を示します。
テーブルt1の定義は以下のとおりです。c1列には2つの値しかなく、値0が全データの1%、値1が全データの99%を占めていると仮定します。c2列には1から10までの10個の値があり、それぞれ10%ずつを占めています。すると、SELECT * FROM t1 WHERE c1 = ? AND c2 = ?というSQLに対して、計画生成時にオプティマイザーが見た条件がc1 = 0 and c2 = 1だった場合、オプティマイザーはインデックスidx1を使用することを選択します。まずインデックスからc1 = 0を満たす1%のデータを取得し、その後インデックスを使ってテーブルに戻り、C2 = 1で条件を満たさないデータをフィルタリングして返します。この場合、1%のデータをスキャンし、インデックスを使ってテーブルに戻る1%のデータをスキャンするだけでクエリが完了します。しかし、もしその後 server が別のSQLを受信し、ユーザーが指定した条件がc1 = 1 and c2 = 1に変わった場合、以前に生成された計画を再利用すると、まずインデックスidx1からC1 = 1を満たす99%のデータを取得し、その後インデックスを使ってテーブルに戻り、C2 = 1で条件を満たさないデータをフィルタリングして返すことになります。この場合、99%のデータをスキャンし、インデックスを使ってテーブルに戻る99%のデータをスキャンする必要があります。明らかに、このSQLに対してはインデックスidx2を使用する方が良い選択です。なぜなら、どのような値を取る場合でも、idx2を使用する場合は常に10%のデータをスキャンし、テーブルに戻る10%のデータをスキャンするだけで済むため、SQLの実行時間がより安定するからです。
create table t1 (
c1 int,
c2 int,
c3 int,
key idx1 (c1),
key idx2 (c2)
);
このような場合、ユーザーはoutlineを使用してSQLに実行計画をバインドし、このクエリでidx2インデックスを使用するよう強制できます(データベースやテーブルをシャーディングする場合は、1つのテンプレートOutlineですべてのシャードテーブルをカバーでき、テーブルごとに作成する必要はありません)。もちろん、このSQLに対しては実際にはもっと良い最適化方法がありますが、これは本記事の要旨とは異なるため、詳細には触れません。
プランバインディング
プランバインディングは、OceanBaseデータベースが提供するメカニズムであり、SQLを変更することなく、データベース内にoutlineを作成することで実行計画を制御できるようにします。OceanBaseデータベースは、2つの方法でOutlineを作成できます。1つはSQL_TEXT(ユーザーが実行するパラメータ付きの元のステートメント)を使用する方法で、もう1つはSQL_IDを使用する方法です。
V4.4.2 BP3より前のバージョンでは、Outlineを作成するには、対応するデータベースで実行する必要がありました。OceanBaseデータベースV4.4.2では、V4.4.2 BP3バージョンからBINDING_RULE句が導入され、Outlineの適用範囲を設定するために使用されます。この機能には以下の特徴があります:
- データベース間バインディング:1つのOutlineは、テナント内のすべての同名テーブルに適用されます。
- テンプレートバインディング:
${var:regex}を使用してテーブル名やデータベース名をワイルドカードで表現することで、1つのOutlineで任意の命名規則を持つデータベースやテーブルのシャーディングをカバーできます。 - 複数テーブル変数制約:
JOINで複数のシャードテーブルを結合する場合、同一の変数は常に同じ値を取る必要があります。これにより、誤ったマッチングを防ぐことができます。 - 完全な後方互換性:
BINDING_RULEを明示的に記述しない場合、その動作は過去のバージョン(V4.4.2 BP3より前)と一致します。
SQL_TEXTを使用してOutlineを作成する
SQL_TEXTを使用してOutlineを作成すると、Key-Valueペアが生成され、Mapに格納されます。ここで、KeyはバインドされたSQLのパラメータ化後のテキスト、ValueはバインドされたHintです。具体的なパラメータ化の原則については、クイックパラメータ化を参照してください。
SQL_TEXTを使用してOutlineを作成する構文は以下のとおりです:
CREATE [OR REPLACE] OUTLINE outline_name ON stmt
[TO target_stmt
| BINDING_RULE (binding_item)];
binding_item:
SCOPE = {DATABASE | TENANT}
| MAP = {real_name TO 'pattern', ...}
パラメータの説明は以下のとおりです:
OR REPLACE:オプションです。OR REPLACEを指定すると、既存の実行計画を置き換えることができます。outline_name:作成するOutlineの名前を指定します。stmt:通常はHintと元のパラメータを持つDMLステートメントです。TO target_stmt:TO target_stmtを指定しない場合、データベースが受け付けたSQLをパラメータ化したテキストと、stmtからHintを除去してパラメータ化したテキストが一致すれば、そのSQLにstmt内のHintをバインドして実行計画を生成します。Hintを含むステートメントに対して固定の計画を実行したい場合は、元のSQLを指定するためにTO target_stmtが必要です。例:obclient> CREATE OUTLINE outline1 ON SELECT /*+NO_REWRITE*/ * FROM tbl1 WHERE col1 = 4 AND col2 = 6 ORDER BY 2 TO SELECT * FROM tbl1 WHERE col1 = 4 AND col2 = 6 ORDER BY 2;注意
target_stmtを使用する場合、stmtとtarget_stmtがHintを除去した後完全に一致することが厳密に要求されます。BINDING_RULE (binding_item):オプションです。このOutlineの適用範囲を指定するために使用されます。説明
V4.4.2バージョンでは、V4.4.2 BP3バージョンから
BINDING_RULE句が導入されました。SCOPE = {DATABASE | TENANT}:SCOPE = DATABASE:デフォルト値で、現在のデータベース内でのみ有効です。SCOPE = TENANT:現在のテナント内のすべてのデータベースで有効です。
MAP = {real_name TO 'pattern', ...}:左側の実際のテーブル名/db.tableに右側のpatternを適用します。patternには${変数名}または${変数名:正規表現}を記述できます。例については、以下の 現在のデータベース内のシャードテーブルのワイルドカード指定 の内容を参照してください。注意
MAPの左側がdb.table形式の場合、右側のpatternではデータベース名とテーブル名のパターンをそれぞれ単一引用符で囲み、ドットで区切る必要があります:'db_pattern'.'table_pattern'。- 同一の
BINDING_RULE内では、変数は定義後に参照する必要があります。同名の変数は、すべての参照箇所で一貫した値を取る必要があります。
SQL_IDを使用してアウトラインを作成する
SQL_ID を使用してアウトラインを作成する構文は以下のとおりです:
obclient> CREATE OUTLINE outline_name ON sql_id USING HINT hint_text;
SQL_ID は、バインド対象のSQLに対応する SQL_ID であり、以下の方法で取得できます:
V$OB_PLAN_CACHE_PLAN_STATをクエリすることで取得します。GV$OB_SQL_AUDITをクエリすることで取得します。パラメータ化された元のSQLを使用し、MD5で
SQL_IDを生成します。以下のスクリプトを参考に、対応するSQLのSQL_IDを生成できます。IMPORT hashlib sql_text='SELECT * FROM t1 WHERE c2 = ?' sql_id=hashlib.md5(sql_text.encode('utf-8')).hexdigest().upper() PRINT(sql_id)
SQL_ID を使用してアウトラインをバインドする例は以下のとおりです:
obclient> CREATE OUTLINE otl_idx_c2
ON 'ED570339F2C856BA96008A29EDF04C74'
USING HINT /*+ INDEX(t1 idx_c2)*/ ;
シャーディングシナリオにおけるテンプレートバインド
クロスデータベースの同名テーブルバインド
シャーディングアーキテクチャでは、複数のデータベースに同名のテーブル(例:db_001.orders、db_002.orders)が存在することがあります。SCOPE = TENANT を使用することで、1つのアウトラインですべてのデータベースの同名テーブルをカバーできます。
例:
すべてのデータベースの orders テーブルは idx_status インデックスを使用します:
obclient> CREATE OUTLINE otl_orders
ON SELECT /*+ index(orders, idx_status) */*
FROM orders
WHERE status = 'a'
BINDING_RULE (SCOPE = TENANT);
データベース
db_001内のクエリヒット例。obclient> USE db_001;obclient> SELECT * FROM orders WHERE status = 'pending';データベース
db_002内のクエリヒット例。obclient> USE db_002;obclient> SELECT * FROM orders WHERE status = 'pending';データベース
db_test内のクエリヒット例。obclient> USE db_test;obclient> SELECT * FROM orders WHERE status = 'pending';
現在のデータベース内のシャードテーブルのワイルドカード指定
シャーディングアーキテクチャでは、単一のデータベース内に名前パターンが一致する複数のシャードテーブル(例:orders_sh、orders_hz、orders_bj)が存在します。MAP と正規表現変数を組み合わせることで、1つのアウトラインですべてのシャードテーブルをカバーできます。
例:
orders_xx シリーズのシャードテーブルは、同じアウトラインを共有します:
obclient> CREATE OUTLINE otl_orders_tpl
ON SELECT /*+ index(orders_sh, idx_status) */*
FROM orders_sh
WHERE status = 'a'
BINDING_RULE (MAP = {orders_sh TO 'orders_${S:[a-z]+}'});
クエリヒット例
S=bj。obclient> SELECT * FROM orders_bj WHERE status = 'pending';クエリヒット例
S=hz。obclient> SELECT * FROM orders_hz WHERE status = 'pending';クエリミス例。
001は[a-z]+にマッチしません。obclient> SELECT * FROM orders_001 WHERE status = 'pending';
関連ドキュメント
SQL_TEXT および SQL_ID 方式以外に、CREATE FORMAT OUTLINE を使用してあいまいなアウトラインを作成し、プランバインディングを行うこともできます。この方法はテキストプロトコルとPSプロトコルの両方で有効です。詳細については、CREATE FORMAT OUTLINE(MySQLモード)およびCREATE FORMAT OUTLINE(Oracleモード)を参照してください。