SQL準備実行プランキャッシュ
TiDBは、 PrepareクエリとExecuteクエリの実行プランのキャッシュをサポートしています。これには、準備されたステートメントの両方の形式が含まれます。
COM_STMT_PREPAREおよびCOM_STMT_EXECUTEプロトコル機能を使用します。- SQLステートメント
PREPAREおよびEXECUTEを使用します。
TiDBオプティマイザは、これら2つのタイプのクエリを同じ方法で処理します。準備時に、パラメータ化されたクエリがAST(抽象構文ツリー)に解析され、キャッシュされます。後の実行では、保存されたASTと特定のパラメータ値に基づいて実行プランが生成されます。
実行プランキャッシュが有効になっている場合、最初の実行でPrepareステートメントごとに、現在のクエリが実行プランキャッシュを使用できるかどうかを確認し、クエリで使用できる場合は、生成された実行プランをLRUによって実装されたキャッシュに配置します(最近使用済み)リンクリスト。後続のExecuteのクエリでは、実行プランがキャッシュから取得され、可用性がチェックされます。チェックが成功すると、実行プランを生成するステップはスキップされます。それ以外の場合、実行プランは再生成され、キャッシュに保存されます。
現在のバージョンのTiDBでは、 Prepareのステートメントが次の条件のいずれかを満たしている場合、クエリまたはプランはキャッシュされません。
Unionには、SELECT、およびUPDATEExceptのSQLIntersectが含まれてINSERTDELETE。- クエリは、パーティションテーブルまたは一時テーブル、あるいは生成された列を含むテーブルにアクセスします。
- クエリには、
select * from t where a > (select ...)などのサブクエリが含まれています。 - クエリには、
select /*+ ignore_plan_cache() */ * from tなどのignore_plan_cacheのヒントが含まれています。 - クエリには、
select * from t where a>? and b>@xなどの?以外の変数(システム変数またはユーザー定義変数を含む)が含まれています。 row_countには、キャッシュできない関数がversionれてcurrent_rolecurrent_userlast_insert_iddatabase()likeconnection_iduser。- クエリには、
Limit ?やLimit 10, ?など、Limitの後に?が含まれます。?の特定の値はクエリのパフォーマンスに大きな影響を与えるため、このようなクエリはキャッシュされません。 - クエリには、
Order By ?のようにOrder Byの後に?が含まれます。このようなクエリは、?で指定された列に基づいてデータを並べ替えます。異なる列を対象とするクエリが同じ実行プランを使用している場合、結果は正しくありません。したがって、そのようなクエリはキャッシュされません。ただし、クエリがOrder By a+?などの一般的なクエリである場合は、キャッシュされます。 - クエリには、
Group By?のようにGroup Byの後に?が含まれます。このようなクエリは、?で指定された列に基づいてデータをグループ化します。異なる列を対象とするクエリが同じ実行プランを使用している場合、結果は正しくありません。したがって、そのようなクエリはキャッシュされません。ただし、クエリがGroup By a+?などの一般的なクエリである場合は、キャッシュされます。 - クエリには、
(partition by year order by sale rows ? preceding)などのWindow Frameウィンドウ関数の定義に?が含まれています。ウィンドウ関数の他の場所に?が表示されている場合、クエリはキャッシュされます。 - クエリには、
c_int >= ?やc_int in (?, ?)などのintとstringを比較するためのパラメータが含まれています。ここで、?はset @x='123'などの文字列型を示します。クエリ結果がMySQLと互換性があることを確認するには、各クエリでパラメータを調整して、そのようなクエリがキャッシュされないようにする必要があります。 - プランは
TiFlashにアクセスしようとします。 - ほとんどの場合、現在の
Prepareステートメントにパラメーターがない場合を除き、TableDualを含むプランはキャッシュされません。
Prepareはセッション間で実行できないため、LRUリンクリストはセッションレベルのキャッシュとして設計されていExecute 。 LRUリストの各要素は、キーと値のペアです。値は実行プランであり、キーは次の部分で構成されています。
Executeが実行されるデータベースの名前Prepareステートメントの識別子、つまりPREPAREキーワードの後の名前- 現在のスキーマバージョン。これは、DDLステートメントが正常に実行されるたびに更新されます。
- 実行時のSQLモード
Execute time_zoneシステム変数の値である現在のタイムゾーンsql_select_limitシステム変数の値
上記の情報の変更(たとえば、データベースの切り替え、 Prepareステートメントの名前変更、DDLステートメントの実行、SQLモード/ time_zoneの値の変更)、またはLRUキャッシュ除去メカニズムにより、実行時に実行プランのキャッシュミスが発生します。
実行プランキャッシュがキャッシュから取得された後、TiDBは最初に実行プランがまだ有効かどうかをチェックします。現在のExecuteステートメントが明示的なトランザクションで実行され、参照されるテーブルがトランザクションpre-orderステートメントで変更された場合、このテーブルにアクセスするキャッシュされた実行プランにUnionScan演算子が含まれていないため、実行できません。
検証テストに合格すると、実行プランのスキャン範囲が現在のパラメータ値に従って調整され、データクエリの実行に使用されます。
実行プランのキャッシュとクエリのパフォーマンスについては、注目に値する点がいくつかあります。
- 実行プランがキャッシュされているかどうかに関係なく、SQLバインディングの影響を受けます。キャッシュされていない実行プラン(最初の
Execute)の場合、これらのプランは既存のSQLバインディングの影響を受けます。キャッシュされた実行プランの場合、新しいSQLバインディングが作成されると、これらのプランは無効になります。 - キャッシュされたプランは、統計、最適化ルール、および式によるブロックリストのプッシュダウンの変更による影響を受けません。
Executeのパラメーターが異なることを考慮すると、実行プランキャッシュは、適応性を確保するために特定のパラメーター値に密接に関連するいくつかの積極的なクエリ最適化メソッドを禁止します。これにより、クエリプランが特定のパラメータ値に対して最適でない場合があります。たとえば、クエリのフィルター条件はwhere a > ? And a < ?で、最初のExecuteステートメントのパラメーターはそれぞれ2と1です。これらの2つのパラメーターが次の実行時に1と2になる可能性があることを考慮すると、オプティマイザーは現在のパラメーター値に固有の最適TableDual実行プランを生成しません。- キャッシュの無効化と削除が考慮されていない場合、実行プランキャッシュはさまざまなパラメータ値に適用され、理論的には特定の値に対して最適でない実行プランが発生します。たとえば、フィルター条件が
where a < ?で、最初の実行に使用されるパラメーター値が1の場合、オプティマイザーは最適なIndexScan実行プランを生成し、それをキャッシュに入れます。以降の実行では、値が10000になった場合、TableScanプランの方が適している可能性があります。ただし、実行プランキャッシュにより、以前に生成されたIndexScanが実行に使用されます。したがって、実行プランキャッシュは、クエリが単純で(コンパイルの比率が高い)、実行プランが比較的固定されているアプリケーションシナリオに適しています。
現在、実行プランのキャッシュはデフォルトで無効になっています。この機能を有効にするには、TiDB構成ファイルでprepared-plan-cacheを有効にします。
ノート:
実行プランのキャッシュ機能は
PrepareExecuteにのみ適用され、通常のクエリには有効になりません。
実行プランのキャッシュ機能を有効にした後、セッションレベルのシステム変数last_plan_from_cacheを使用して、前のExecuteのステートメントがキャッシュされた実行プランを使用したかどうかを確認できます。次に例を示します。
MySQL [test]> create table t(a int);
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> prepare stmt from 'select * from t where a = ?';
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> set @a = 1;
Query OK, 0 rows affected (0.00 sec)
-- The first execution generates an execution plan and saves it in the cache.
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
-- The second execution hits the cache.
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 1 |
+------------------------+
1 row in set (0.00 sec)
実行プランキャッシュが原因でPrepareの特定のセットが予期しない動作をすることがわかった場合は、 Execute SQLヒントを使用して、現在のステートメントの実行プランキャッシュの使用をスキップできignore_plan_cache() 。それでも、例として上記のステートメントを使用してください。
MySQL [test]> prepare stmt from 'select /*+ ignore_plan_cache() */ * from t where a = ?';
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> set @a = 1;
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
MySQL [test]> execute stmt using @a;
Empty set (0.00 sec)
MySQL [test]> select @@last_plan_from_cache;
+------------------------+
| @@last_plan_from_cache |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)