SQL 未準備実行プラン キャッシュ
TiDB は、 ステートメントPrepare / Executeと同様に、一部のPREPARE以外のステートメントの実行プランのキャッシュをサポートしています。この機能により、これらのステートメントは最適化フェーズをスキップしてパフォーマンスを向上させることができます。
準備されていないプラン キャッシュを有効にすると、追加のメモリと CPU オーバーヘッドが発生する可能性があり、すべての状況に適しているとは限りません。シナリオでこの機能を有効にするかどうかを判断するには、セクションパフォーマンス上の利点とメモリ監視を参照してください。
原理
非準備プラン キャッシュは、 準備されたプランキャッシュとキャッシュを共有するセッション レベルの機能です。非準備プラン キャッシュの基本原理は次のとおりです。
- 準備されていないプラン キャッシュを有効にすると、TiDB はまず抽象構文ツリー (AST) に基づいてクエリをパラメーター化します。たとえば、
SELECT * FROM t WHERE b < 10 AND a = 1SELECT * FROM t WHERE b < ? and a = ?としてパラメーター化されます。 - 次に、TiDB はパラメータ化されたクエリを使用してプラン キャッシュを検索します。
- 再利用可能なプランが見つかった場合は、それが直接使用され、最適化フェーズはスキップされます。
- それ以外の場合、オプティマイザーは新しいプランを生成し、それをキャッシュに戻して、後続のクエリで再利用します。
使用法
準備されていないプラン キャッシュを有効または無効にするには、 tidb_enable_non_prepared_plan_cacheシステム変数を設定します。また、 tidb_session_plan_cache_sizeシステム変数を使用して準備されていないプラン キャッシュのサイズを制御することもできます。キャッシュされたプランの数がtidb_session_plan_cache_size超えると、TiDB は最も最近使用されていない (LRU) 戦略を使用してプランを削除します。
v7.1.0 以降では、システム変数tidb_plan_cache_max_plan_sizeを使用して、キャッシュできるプランの最大サイズを制御できます。デフォルト値は 2 MB です。プランのサイズがこの値を超えると、プランはキャッシュされません。
注記:
tidb_session_plan_cache_sizeで指定されたメモリは、準備済みプラン キャッシュと準備されていないプラン キャッシュの間で共有されます。現在のクラスターに対して準備済みプラン キャッシュを有効にしている場合、準備されていないプラン キャッシュを有効にすると、元の準備済みプラン キャッシュのヒット率が低下する可能性があります。
例
次の例は、準備されていないプラン キャッシュを使用する方法を示しています。
テスト用にテーブル
tを作成します。CREATE TABLE t (a INT, b INT, KEY(b));準備されていないプラン キャッシュを有効にします。
SET tidb_enable_non_prepared_plan_cache = ON;次の 2 つのクエリを実行します。
SELECT * FROM t WHERE b < 10 AND a = 1; SELECT * FROM t WHERE b < 5 AND a = 2;2 番目のクエリがキャッシュにヒットするかどうかを確認します。
SELECT @@last_plan_from_cache;出力の
last_plan_from_cacheの値が1の場合、2 番目のクエリの実行プランはキャッシュから取得されることを意味します。+------------------------+ | @@last_plan_from_cache | +------------------------+ | 1 | +------------------------+ 1 row in set (0.00 sec)
制限
最適でない計画をキャッシュする
TiDB は、パラメータ化されたクエリに対して 1 つのプランのみをキャッシュします。たとえば、クエリSELECT * FROM t WHERE a < 1とSELECT * FROM t WHERE a < 100000同じパラメータ化された形式SELECT * FROM t WHERE a < ?を共有しているため、同じプランを共有します。
これによりパフォーマンスの問題が発生する場合は、 ignore_plan_cache()ヒントを使用してキャッシュ内のプランを無視し、オプティマイザーが毎回 SQL の新しい実行プランを生成するようにすることができます。SQL を変更できない場合は、バインディングを作成して問題を解決できます。たとえば、 CREATE BINDING FOR SELECT ... USING SELECT /*+ ignore_plan_cache() */ ...です。
使用制限
前述のリスクと、実行プラン キャッシュが大きなメリットをもたらすのは単純なクエリに対してのみであるという事実 (クエリが複雑で実行に時間がかかる場合、実行プラン キャッシュを使用してもあまり役に立たない可能性があります) のため、TiDB では、準備されていないプラン キャッシュの範囲に厳しい制限が設けられています。制限は次のとおりです。
- 準備されたプランキャッシュでサポートされていないクエリまたはプランは、準備されていないプラン キャッシュでもサポートされません。
WindowやHavingなどの複雑な演算子を含むクエリはサポートされていません。- 3 つ以上の
Joinテーブルまたはサブクエリを含むクエリはサポートされていません。 ORDER BY 1やGROUP BY a+1など、ORDER BYまたはGROUP BY直後に数字または式が含まれるクエリはサポートされていません。ORDER BY column_nameとGROUP BY column_nameのみがサポートされています。SELECT * FROM t WHERE json_col = '{}'など、JSON、ENUM、SET、またはBITタイプの列でフィルタリングするクエリはサポートされていません。SELECT * FROM t WHERE a is NULLなど、NULL値でフィルタリングするクエリはサポートされていません。- パラメータ化後のパラメータ数が 200 を超えるクエリ (
SELECT * FROM t WHERE a in (1, 2, 3, ... 201)など) は、デフォルトではサポートされません。v7.3.0 以降では、tidb_opt_fix_controlシステム変数に44823修正を設定することで、この制限を変更できます。 - 仮想列、一時テーブル、ビュー、またはメモリテーブルにアクセスするクエリはサポートされていません (例:
SELECT * FROM INFORMATION_SCHEMA.COLUMNS、COLUMNSは TiDBメモリテーブル)。 - ヒントまたはバインディングを含むクエリはサポートされていません。
- DML ステートメントまたは
FOR UPDATE句を含むSELECTステートメントは、デフォルトではサポートされていません。この制限を削除するには、SET tidb_enable_non_prepared_plan_cache_for_dml = ON実行します。
この機能を有効にすると、オプティマイザーはクエリを迅速に評価します。準備されていないプラン キャッシュのサポート条件を満たしていない場合、クエリは通常の最適化プロセスに戻ります。
パフォーマンス上の利点
内部テストでは、準備されていないプラン キャッシュ機能を有効にすると、ほとんどの TP シナリオでパフォーマンスが大幅に向上します。たとえば、TPC-C テストでは約 4%、一部の銀行業務ワークロードでは 10% 以上、Sysbench RangeScan では 15% のパフォーマンス向上が見られます。
ただし、この機能により、クエリがサポートされているかどうかの判断、クエリのパラメータ化、キャッシュ内のプランの検索など、追加のメモリと CPU のオーバーヘッドも発生します。ワークロード内のクエリの大部分をキャッシュで処理できない場合、キャッシュを有効にするとパフォーマンスに悪影響が出る可能性があります。
この場合、Grafana のQueries Using Plan Cache OPSパネルのnon-preparedメトリックとPlan Cache Miss OPSパネルのnon-prepared-unsupportedメトリックを観察する必要があります。ほとんどのクエリがサポートされておらず、プラン キャッシュにヒットできるクエリが少数の場合は、この機能を無効にすることができます。

診断
準備されていないプラン キャッシュを有効にした後、 EXPLAIN FORMAT='plan_cache' SELECT ...ステートメントを実行して、クエリがキャッシュにヒットできるかどうかを確認できます。キャッシュにヒットできないクエリの場合、システムは警告で理由を返します。
FORMAT='plan_cache'追加しないと、 EXPLAINステートメントはキャッシュにヒットしないことに注意してください。
クエリがキャッシュにヒットするかどうかを確認するには、次のEXPLAIN FORMAT='plan_cache'ステートメントを実行します。
EXPLAIN FORMAT='plan_cache' SELECT * FROM (SELECT a+1 FROM t) t;
出力は次のようになります。
3 rows in set, 1 warning (0.00 sec)
キャッシュにヒットできないクエリを表示するには、 SHOW warnings;実行します。
SHOW warnings;
出力は次のようになります。
+---------+------+-------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+-------------------------------------------------------------------------------+
| Warning | 1105 | skip non-prepared plan-cache: queries that have sub-queries are not supported |
+---------+------+-------------------------------------------------------------------------------+
1 row in set (0.00 sec)
前の例では、準備されていないプラン キャッシュが+操作をサポートしていないため、クエリはキャッシュにヒットできません。
監視
準備されていないプラン キャッシュを有効にすると、次のペインでメモリ使用量、キャッシュ内のプランの数、キャッシュ ヒット率を監視できます。

statements_summaryテーブルとスロー クエリ ログでキャッシュ ヒット率を監視することもできます。次に、 statements_summaryテーブルでキャッシュ ヒット率を表示する方法を示します。
テーブル
tを作成します。CREATE TABLE t (a int);準備されていないプラン キャッシュを有効にします。
SET @@tidb_enable_non_prepared_plan_cache=ON;次の 3 つのクエリを実行します。
SELECT * FROM t WHERE a<1; SELECT * FROM t WHERE a<2; SELECT * FROM t WHERE a<3;キャッシュヒット率を表示するには、
statements_summaryテーブルをクエリします。SELECT digest_text, query_sample_text, exec_count, plan_in_cache, plan_cache_hits FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY WHERE query_sample_text LIKE '%SELECT * FROM %';出力は次のようになります。
+---------------------------------+------------------------------------------+------------+---------------+-----------------+ | digest_text | query_sample_text | exec_count | plan_in_cache | plan_cache_hits | +---------------------------------+------------------------------------------+------------+---------------+-----------------+ | SELECT * FROM `t` WHERE `a` < ? | SELECT * FROM t WHERE a<1 | 3 | 1 | 2 | +---------------------------------+------------------------------------------+------------+---------------+-----------------+ 1 row in set (0.01 sec)出力から、クエリが 3 回実行され、キャッシュに 2 回ヒットしたことがわかります。