パラメータライゼーションとは、SQLクエリ内の定数を変数に置き換えるプロセスです。
ファストパラメータライゼーションによる実行計画の取得
同じSQL文が実行されるたびに異なるパラメータを使用する場合があります。これらのパラメータをパラメータライズ処理することで、具体的なパラメータに依存しないSQL文字列を得て、その文字列を計画キャッシュのキーとして使用し、計画キャッシュから実行計画を取得することができます。これにより、パラメータが異なるSQLでも同じ計画を共有できるようになります。
従来のデータベースでは、パラメータライゼーション時に一般的に構文木をパラメータライズし、その後パラメータライズされた構文木をキーとして計画キャッシュから計画を取得します。一方、OceanBaseデータベースでは、テキスト列を直接パラメータライズして計画キャッシュのキーとして使用するため、これをファストパラメータライゼーションと呼んでいます。
ファストパラメータライゼーションに基づいて実行計画を取得するプロセスは、以下の図に示されています:
ファストパラメータライゼーションに基づく実行計画キャッシュの利点は以下の通りです:
構文解析プロセスを省略できます。
Hash Mapの検索時に、パラメータライズされた構文木に対するハッシュと比較操作を、テキスト列に対するハッシュと
MEMCMP操作に置き換えることで、実行効率を向上させることができます。
定数のパラメータライゼーションと制約条件
OceanBaseデータベースでは、特定のシナリオにおいて定数はパラメータライズできません。すなわち、パラメータライゼーションの制約条件は以下の通りです:
すべての
ORDER BY句の後の定数(例:ORDER BY 1,2;)。すべての
GROUP BY句の後の定数(例:GROUP BY 1,2;)。フォーマット文字列としての文字列定数(例:SQL文
SELECT DATE_FORMAT('2006-06-00', '%d');の%d)。関数の入力パラメータのうち、関数の結果に影響を与え、最終的に実行計画に影響を与える定数(例:SQL文
CAST(999.88 as NUMBER(2,1))のNUMBER(2,1)、またはSUBSTR('abcd', 1, 2)の1と2)。関数の入力パラメータのうち、暗黙の情報を含み、最終的に実行計画に影響を与える定数(例:SQL文
SELECT UNIX_TIMESTAMP('2015-11-13 10:20:19.012');の "2015-11-13 10:20:19.012" は、入力タイムスタンプを指定すると同時に、関数処理の精度値がミリ秒であることを暗黙的に指定しています)。
一般的な定数パラメータライゼーションの例
既存のSQLクエリ文の例を以下に示します:
obclient> SELECT * FROM t1 WHERE c1 = 5 AND c2 ='oceanbase';
上記の例でのSQLクエリは、OceanBaseデータベース内部でパラメータライゼーション処理を行った後の結果は以下の通りです。定数 5 と oceanbase はパラメータライズされ、変数 @1 と @2 に変わり、現在データベース内で実行されたことのあるSQLと照合し、対応する実行計画をマッチングします。
obclient> SELECT * FROM T1 WHERE c1 = @1 AND c2 = @2;
定数のパラメータライゼーションがサポートされない例
しかし、プランマッチングではすべての定数がパラメータ化できるわけではありません。例えば、ORDER BY 句の後ろに続く定数は、SELECT で投影された列のうち、何番目の列に従ってソートするかを示しているため、パラメータ化することはできません。
サンプルテーブル t1 を作成し、適切なデータを挿入します。テーブル t1 には c1 と c2 の2つの列が含まれており、c1 が主キー列です。
obclient> CREATE TABLE t1(c1 INT PRIMARY KEY,c2 INT);
Query OK, 0 rows affected
obclient> INSERT INTO t1 VALUES (1,2);
Query OK, 1 row affected
obclient> INSERT INTO t1 VALUES (2,1);
Query OK, 1 row affected
obclient> INSERT INTO t1 VALUES (3,1);
Query OK, 1 row affected
以下のSQLクエリを実行し、結果を
c1列でソートします。c1は主キー列として順序付けられているため、主キーによるアクセスを使用することでソートを省略できます。obclient> SELECT c1, c2 FROM t1 ORDER BY 1; +----+------+ | C1 | C2 | +----+------+ | 1 | 2 | | 2 | 1 | | 3 | 1 | +----+------+ 3 rows in set obclient> EXPLAIN SELECT c1, c2 FROM t1 ORDER BY 1; Query Plan: | =================================== |ID|OPERATOR |NAME|EST. ROWS|COST| ----------------------------------- |0 |TABLE SCAN|t1 |1000 |1381| =================================== Outputs & filters: ------------------------------------- 0 - output([T1.C1], [T1.C2]), filter(nil), access([T1.C1], [T1.C2]), partitions(p0)以下のSQLクエリを実行し、結果を
c2列でソートする場合、明示的なソート操作を実行する必要があります。実行計画は以下の例のようになります:obclient> SELECT c1, c2 FROM t1 ORDER BY 2; +----+------+ | C1 | C2 | +----+------+ | 2 | 1 | | 3 | 1 | | 1 | 2 | +----+------+ 3 rows in set obclient> EXPLAIN SELECT c1, c2 FROM t1 ORDER BY 2; Query Plan: | ==================================== |ID|OPERATOR |NAME|EST. ROWS|COST| ------------------------------------ |0 |SORT | |1000 |1886| |1 | TABLE SCAN|t1 |1000 |1381| ==================================== Outputs & filters: ------------------------------------- 0 - output([T1.C1], [T1.C2]), filter(nil), sort_keys([T1.C2, ASC]) 1 - output([T1.C1], [T1.C2]), filter(nil), access([T1.C1], [T1.C2]), partitions(p0)
したがって、ORDER BY 句の後ろの定数をパラメータ化すると、異なる ORDER BY の値が同じパラメータ化されたSQLを持つことになり、誤った計画がヒットする原因となります。
定数パラメータ化による誤マッチ問題の解決
定数パラメータ化における潜在的な誤マッチ問題を解決するため、ハードパースによる実行計画の生成過程で、SQLリクエストに対して構文木解析を用いたパラメータ化を行い、不整合な情報を特定します。例えば、ある文に対応する情報が「高速パラメータ化によるパラメータ配列の3番目の要素は数字3でなければならない」というものであれば、これを「制約条件」と呼ぶことができます。
Q1クエリ SELECT c1, c2, c3 FROM t1 WHERE c1 = 1 AND c2 LIKE 'senior%' ORDER BY 3; について、構文解析を行うと、パラメータ化されたSQL文は以下のようになります。パラメータ化配列は {1,'senior%',3} です。
obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 AND c2 LIKE @2 ORDER BY @3;
ORDER BY 句の後ろの定数が異なる場合、同じ実行計画を共有することはできません。そのため、構文木解析によるパラメータ化では別のパラメータ化結果が得られます。以下の例のように、Q1クエリに対応するパラメータ化配列は {1,'senior'} となり、制約条件は「高速パラメータ化によるパラメータ配列の3番目の要素は数字3でなければならない」となります。OceanBaseデータベースは、Q1クエリに対して新しく生成されたパラメータ化テキスト、制約条件、および実行計画をすべて計画キャッシュに格納します。
obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 AND c2 LIKE @2 ORDER BY 3;
ユーザーが再度Q2クエリコマンド SELECT c1, c2, c3 FROM t1 WHERE c1 = 1 AND c2 LIKE 'senior%' ORDER BY 2; を発行すると、高速パラメータ化の結果は以下の例のようになり、対応するパラメータ化配列は {1,'senior%',2} となります。
obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 and c2 like @2 ORDER BY @3;
これはQ1クエリの高速パラメータ化後のSQL結果と同じですが、「高速パラメータ化によるパラメータ配列の3番目の要素は数字3でなければならない」という制約条件を満たさないため、その計画とマッチすることはできません。この場合、Q2はハードパースにより新しい実行計画および制約条件(すなわち「高速パラメータ化によるパラメータ配列の3番目の要素は数字2でなければならない」)を生成し、新しい計画と制約条件を計画キャッシュに追加します。これにより、次回Q1およびQ2を実行する際には、それぞれ正しい実行計画がヒットするようになります。
パラメータ化実行計画の区別戦略
OceanBaseデータベースは、Hintを使用してSQL文内のリテラルに対するパラメータ置換を有効にするかどうかを制御することもサポートしています。詳細については、CURSOR_SHARING_EXACT Hintを参照してください。
一つのSQLリクエストについて、以下の戦略を用いて、そのSQLがパラメータ化された計画にヒットするか、非パラメータ化された計画にヒットするかを区別します:
cursor_sharingを確認するまず、
cursor_sharingシステム変数を確認します。cursor_sharingの値が異なるSQL文は必ず異なる計画にヒットします。cursor_sharingを参照してください。query_sqlを確認するcursor_sharing変数だけではパラメータ化計画か非パラメータ化計画かを判断できない場合、cursor_sharingがEXACTモードであることを意味します。これはCURSOR_SHARING_EXACTHintによって制御されています。Hintを追加するとquery_sqlの生成に影響を与えるため、query_sqlを通じてパラメータ化計画と非パラメータ化計画を区別できます。query_sqlはGV$OB_SQL_AUDITまたはGV$OB_PLAN_CACHE_PLAN_STATビューで確認できます。