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が実行に使用されます。したがって、実行プラン キャッシュは、クエリが単純で (コンパイルの比率が高く)、実行プランが比較的固定されているアプリケーション シナリオにより適しています。

v6.1.0 以降、実行プランのキャッシュはデフォルトで有効になっています。システム変数tidb_enable_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)

プリペアドプランキャッシュのメモリ管理

プリペアドプランキャッシュを使用すると、メモリ オーバーヘッドが発生します。内部テストでは、キャッシュされた各プランは平均 100 KiB のメモリを消費します。 Plan Cache は現在SESSIONレベルであるため、合計メモリ消費量は約the number of sessions * the average number of cached plans in a session * 100 KiBです。

たとえば、現在の TiDB インスタンスには 50 の同時実行セッションがあり、各セッションには約 100 のキャッシュされたプランがあります。合計メモリ消費量は約50 * 100 * 100 KiB = 512 MBです。

システム変数tidb_prepared_plan_cache_sizeを構成することにより、各セッションでキャッシュできるプランの最大数を制御できます。さまざまな環境での推奨値は次のとおりです。

  • TiDBサーバーインスタンスのメモリしきい値が <= 64 GiB の場合、 tidb_prepared_plan_cache_sizeから50を設定します。
  • TiDBサーバーインスタンスのメモリしきい値が > 64 GiB の場合は、 tidb_prepared_plan_cache_size100に設定します。

TiDBサーバーの未使用メモリが特定のしきい値を下回ると、プラン キャッシュのメモリ保護メカニズムがトリガーされ、キャッシュされたプランの一部が削除されます。

システム変数tidb_prepared_plan_cache_memory_guard_ratioを構成することにより、しきい値を制御できます。しきい値はデフォルトで 0.1 です。これは、TiDBサーバーの未使用メモリが合計メモリの 10% 未満になると (メモリの 90% が使用される)、メモリ保護メカニズムがトリガーされることを意味します。

メモリの制限により、プラン キャッシュが失われることがあります。 Grafana ダッシュボードでPlan Cache Miss OPSメトリックを表示して、ステータスを確認できます。

実行計画のキャッシュをクリアする

ADMIN FLUSH [SESSION | INSTANCE] PLAN_CACHEステートメントを実行すると、実行プランのキャッシュをクリアできます。

このステートメントの[SESSION | INSTANCE]は、現在のセッションまたは TiDB インスタンス全体のプラン キャッシュをクリアするかどうかを指定します。スコープが指定されていない場合、上記のステートメントはデフォルトでSESSIONキャッシュに適用されます。

以下は、 SESSION実行プランのキャッシュをクリアする例です。

MySQL [test]> create table t (a int); Query OK, 0 rows affected (0.00 sec) MySQL [test]> prepare stmt from 'select * from t'; Query OK, 0 rows affected (0.00 sec) MySQL [test]> execute stmt; Empty set (0.00 sec) MySQL [test]> execute stmt; Empty set (0.00 sec) MySQL [test]> select @@last_plan_from_cache; -- Select the cached plan +------------------------+ | @@last_plan_from_cache | +------------------------+ | 1 | +------------------------+ 1 row in set (0.00 sec) MySQL [test]> admin flush session plan_cache; -- Clear the cached plan of the current session Query OK, 0 rows affected (0.00 sec) MySQL [test]> execute stmt; Empty set (0.00 sec) MySQL [test]> select @@last_plan_from_cache; -- The cached plan cannot be selected again, because it has been cleared +------------------------+ | @@last_plan_from_cache | +------------------------+ | 0 | +------------------------+ 1 row in set (0.00 sec)

現在、TiDB はGLOBALの実行プラン キャッシュのクリアをサポートしていません。つまり、TiDB クラスター全体のキャッシュされたプランをクリアすることはできません。 GLOBALの実行プランのキャッシュをクリアしようとすると、次のエラーが報告されます。

MySQL [test]> admin flush global plan_cache; ERROR 1105 (HY000): Do not support the 'admin flush global scope.'

COM_STMT_CLOSEコマンドとDEALLOCATE PREPAREステートメントを無視する

SQL ステートメントの構文解析コストを削減するには、 1 をprepare stmt回実行してから、 deallocate prepareを実行する前にexecute stmtを複数回実行することをお勧めします。

MySQL [test]> prepare stmt from '...'; -- Prepare once MySQL [test]> execute stmt using ...; -- Execute once MySQL [test]> ... MySQL [test]> execute stmt using ...; -- Execute multiple times MySQL [test]> deallocate prepare stmt; -- Release the prepared statement

実際には、以下に示すように、 execute stmtを実行した後に毎回deallocate prepareを実行することに慣れているかもしれません。

MySQL [test]> prepare stmt from '...'; -- Prepare once MySQL [test]> execute stmt using ...; MySQL [test]> deallocate prepare stmt; -- Release the prepared statement MySQL [test]> prepare stmt from '...'; -- Prepare twice MySQL [test]> execute stmt using ...; MySQL [test]> deallocate prepare stmt; -- Release the prepared statement

このような場合、最初に実行されたステートメントによって取得されたプランは、2 番目に実行されたステートメントでは再利用できません。

この問題に対処するには、システム変数tidb_ignore_prepared_cache_close_stmtからONを設定して、TiDB がコマンドを無視してprepare stmtを閉じるようにします。

mysql> set @@tidb_ignore_prepared_cache_close_stmt=1; -- Enable the variable Query OK, 0 rows affected (0.00 sec) mysql> prepare stmt from 'select * from t'; -- Prepare once Query OK, 0 rows affected (0.00 sec) mysql> execute stmt; -- Execute once Empty set (0.00 sec) mysql> deallocate prepare stmt; -- Release after the first execute Query OK, 0 rows affected (0.00 sec) mysql> prepare stmt from 'select * from t'; -- Prepare twice Query OK, 0 rows affected (0.00 sec) mysql> execute stmt; -- Execute twice Empty set (0.00 sec) mysql> select @@last_plan_from_cache; -- Reuse the last plan +------------------------+ | @@last_plan_from_cache | +------------------------+ | 1 | +------------------------+ 1 row in set (0.00 sec)