これはアーカイブされた TiDB のドキュメントであり、更新は行われていません。最新の LTS バージョンのドキュメントを表示する

SQL 準備実行計画キャッシュ

TiDB は、 PrepareおよびExecuteクエリの実行プランのキャッシュをサポートしています。これには、プリペアド ステートメントの両方の形式が含まれます。

  • COM_STMT_PREPAREおよびCOM_STMT_EXECUTEプロトコル機能の使用。
  • SQL ステートメントPREPAREおよびEXECUTEを使用します。

TiDB オプティマイザは、これら 2 種類のクエリを同じ方法で処理します。準備時に、パラメータ化されたクエリは AST (Abstract Syntax Tree) に解析され、キャッシュされます。後の実行では、保存された AST と特定のパラメータ値に基づいて実行計画が生成されます。

実行プラン キャッシュが有効な場合、最初の実行でPrepareステートメントごとに、現在のクエリが実行プラン キャッシュを使用できるかどうかがチェックされ、クエリがそれを使用できる場合は、生成された実行プランが LRU (Least Recent) によって実装されたキャッシュに入れられます。使用) リンクされたリスト。後続のExecuteのクエリでは、実行プランがキャッシュから取得され、可用性がチェックされます。チェックが成功した場合、実行計画を生成するステップはスキップされます。それ以外の場合は、実行計画が再生成され、キャッシュに保存されます。

現在のバージョンの TiDB では、 Prepareステートメントが次の条件のいずれかを満たす場合、クエリまたはプランはキャッシュされません。

  • クエリにSELECTUPDATEINSERTDELETEUnionIntersect 、およびExcept以外の SQL ステートメントが含まれています。
  • クエリは、パーティション テーブルまたは一時テーブル、または生成された列を含むテーブルにアクセスします。
  • クエリには、 select * from t where a > (select ...)などのサブクエリが含まれています。
  • クエリには、 select /*+ ignore_plan_cache() */ * from tなどのignore_plan_cacheヒントが含まれています。
  • クエリに、 select * from t where a>? and b>@xなどの?以外の変数 (システム変数またはユーザー定義変数を含む) が含まれています。
  • クエリには、キャッシュできない関数が含まれています: database()current_usercurrent_roleuserconnection_idlast_insert_idrow_countversion 、およびlike
  • クエリには、 Limit ?Limit 10, ?など、 Limitの後に?が含まれます。特定の値?がクエリのパフォーマンスに大きな影響を与えるため、このようなクエリはキャッシュされません。
  • クエリには、 Order By ?など、 Order Byの後に?が含まれます。このようなクエリは、 ?で指定された列に基づいてデータを並べ替えます。異なる列をターゲットとするクエリが同じ実行プランを使用する場合、結果は間違ったものになります。したがって、そのようなクエリはキャッシュされません。ただし、クエリがOrder By a+?などの一般的なものである場合は、キャッシュされます。
  • クエリには、 Group By?など、 Group Byの後に?が含まれます。このようなクエリは、 ?で指定された列に基づいてデータをグループ化します。異なる列をターゲットとするクエリが同じ実行プランを使用する場合、結果は間違ったものになります。したがって、そのようなクエリはキャッシュされません。ただし、クエリがGroup By a+?などの一般的なものである場合は、キャッシュされます。
  • クエリには、ウィンドウ関数Window Frameの定義に(partition by year order by sale rows ? preceding)などの?が含まれています。ウィンドウ関数の他の場所に?が表示される場合、クエリはキャッシュされます。
  • クエリには、 intstringを比較するためのパラメーター ( c_int >= ?c_int in (?, ?)など) が含まれ、 ?set @x='123'などの文字列型を示します。クエリ結果が MySQL と互換性があることを確認するには、各クエリでパラメーターを調整する必要があるため、そのようなクエリはキャッシュされません。
  • プランはTiFlashへのアクセスを試みます。
  • ほとんどの場合、現在のPrepareステートメントにパラメーターがない場合を除き、 TableDualを含むプランはキャッシュされません。

Prepare / Executeはセッション間で実行できないため、LRU リンク リストはセッション レベルのキャッシュとして設計されています。 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ステートメントのパラメーターはそれぞれ21です。これら 2 つのパラメーターが次回の実行時に12になる可能性があることを考慮すると、オプティマイザーは現在のパラメーター値に固有の最適なTableDual実行計画を生成しません。
  • キャッシュの無効化と削除が考慮されていない場合、実行計画のキャッシュがさまざまなパラメータ値に適用され、理論的には、特定の値に対して最適でない実行計画が発生します。たとえば、フィルタ条件がwhere a < ?で、最初の実行に使用されるパラメータ値が1の場合、オプティマイザは最適なIndexScan実行計画を生成してキャッシュに入れます。その後の実行で、値が10000になった場合は、 TableScanのプランの方が優れている可能性があります。ただし、実行計画のキャッシュにより、以前に生成されたIndexScanが実行に使用されます。したがって、実行プラン キャッシュは、クエリが単純で (コンパイルの比率が高く)、実行プランが比較的固定されているアプリケーション シナリオにより適しています。

現在、実行計画のキャッシュはデフォルトで無効になっています。この機能を有効にするには、TiDB 構成ファイルでprepared-plan-cacheを有効にします。

ノート:

実行プラン キャッシュ機能は、 Prepare / Executeクエリにのみ適用され、通常のクエリには影響しません。

実行計画キャッシュ機能を有効にした後、セッション レベルのシステム変数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の特定のセットに実行計画キャッシュが原因で予期しない動作があることがわかった場合は、 ignore_plan_cache() SQL ヒントを使用して、現在のステートメントの実行計画キャッシュの使用をスキップできます。それでも、上記のステートメントを例として使用してください。

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)