インデックスの選択
ストレージエンジンからのデータの読み取りは、SQL実行中に最も時間のかかる手順の1つです。現在、TiDBは、さまざまなストレージエンジンおよびさまざまなインデックスからのデータの読み取りをサポートしています。クエリ実行のパフォーマンスは、適切なインデックスを選択するかどうかに大きく依存します。
このドキュメントでは、テーブルにアクセスするためのインデックスを選択する方法と、インデックスの選択を制御するためのいくつかの関連する方法を紹介します。
テーブルにアクセスする
インデックスの選択を導入する前に、TiDBがテーブルにアクセスする方法、各方法でトリガーされるもの、各方法でどのような違いが生じるか、および長所と短所を理解することが重要です。
テーブルにアクセスするための演算子
オペレーター | トリガー条件 | 該当するシナリオ | 説明 |
---|---|---|---|
PointGet / BatchPointGet | 1つ以上のシングルポイント範囲のテーブルにアクセスする場合。 | 任意のシナリオ | トリガーされた場合、コプロセッサー・インターフェースを呼び出すのではなく、kvgetインターフェースを直接呼び出して計算を実行するため、通常は最速のオペレーターと見なされます。 |
TableReader | なし | 任意のシナリオ | これは一般に、TiKVレイヤーから直接テーブルデータをスキャンする最も効率の悪いオペレーターと見なされています。 _tidb_rowid 列に範囲クエリがある場合、またはテーブルにアクセスして選択する他の演算子がない場合にのみ選択できます。 |
TableReader | テーブルには、TiFlashノードにレプリカがあります。 | 読み取る列は少なくなりますが、評価する行は多くなります。 | Tiflashは列ベースのストレージです。少数の列と多数の行を計算する必要がある場合は、この演算子を選択することをお勧めします。 |
IndexReader | テーブルには1つ以上のインデックスがあり、計算に必要な列がインデックスに含まれています。 | インデックスに狭い範囲のクエリがある場合、またはインデックス付き列の順序要件がある場合。 | 複数のインデックスが存在する場合、コスト見積もりに基づいて適切なインデックスが選択されます。 |
IndexLookupReader | テーブルには1つ以上のインデックスがあり、計算に必要な列がインデックスに完全に含まれているわけではありません。 | IndexReaderと同じです。 | インデックスは計算列を完全にはカバーしていないため、TiDBはインデックスを読み取った後にテーブルから行を取得する必要があります。 IndexReaderオペレーターと比較して追加コストがかかります。 |
ノート:
TableReader演算子は
_tidb_rowid
列のインデックスに基づいており、TiFlashは列ストレージインデックスを使用するため、インデックスの選択はテーブルにアクセスするための演算子の選択です。
インデックス選択ルール
TiDBは、ルールまたはコストに基づいてインデックスを選択します。ベースのルールには、事前ルールとスカイライン剪定が含まれます。インデックスを選択するとき、TiDBは最初にプレルールを試行します。インデックスが事前ルールを満たしている場合、TiDBはこのインデックスを直接選択します。それ以外の場合、TiDBはスカイラインプルーニングを使用して不適切なインデックスを除外し、テーブルにアクセスする各オペレーターのコスト見積もりに基づいて、コストが最も低いインデックスを選択します。
ルールベースの選択
事前ルール
TiDBは、次のヒューリスティックな事前ルールを使用してインデックスを選択します。
ルール1:インデックスが「完全一致の一意のインデックス+テーブルから行を取得する必要がない(つまり、インデックスによって生成されるプランがIndexReader演算子である)」を満たす場合、TiDBはこのインデックスを直接選択します。
ルール2:インデックスが「完全一致の一意のインデックス+テーブルから行を取得する必要がある(つまり、インデックスによって生成されるプランがIndexReader演算子である)」を満たす場合、TiDBは行数が最も少ないインデックスを選択します。候補インデックスとしてテーブルから取得されます。
ルール3:インデックスが「通常のインデックス+テーブルから行を取得する必要がない+読み取る行数が特定のしきい値の値より少ない」を満たす場合、TiDBは行数が最も少ないインデックスを選択します。候補インデックスとして読み取ります。
ルール4:ルール2および3に基づいて候補インデックスが1つだけ選択されている場合は、この候補インデックスを選択します。ルール2と3に基づいてそれぞれ2つの候補インデックスを選択する場合は、読み取る行数が少ないインデックスを選択します(インデックスを持つ行の数+テーブルから取得する行の数)。
上記のルールの「完全一致のインデックス」は、インデックスが付けられた各列の条件が等しいことを意味します。 EXPLAIN FORMAT = 'verbose' ...
ステートメントを実行するときに、事前ルールがインデックスと一致する場合、TiDBは、インデックスが事前ルールと一致することを示すNOTEレベルの警告を出力します。
次の例では、インデックスidx_b
がルール2の「完全一致の一意のインデックス+テーブルから行を取得する必要がある」という条件を満たしているため、TiDBはインデックスidx_b
をアクセスパスとして選択し、 SHOW WARNING
は次のことを示すメモを返します。インデックスidx_b
は事前ルールと一致します。
mysql> CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE INDEX idx_b(b));
Query OK, 0 rows affected (0.01 sec)
mysql> EXPLAIN FORMAT = 'verbose' SELECT b, c FROM t WHERE b = 3 OR b = 6;
+-------------------+---------+---------+------+-------------------------+------------------------------+
| id | estRows | estCost | task | access object | operator info |
+-------------------+---------+---------+------+-------------------------+------------------------------+
| Batch_Point_Get_5 | 2.00 | 8.80 | root | table:t, index:idx_b(b) | keep order:false, desc:false |
+-------------------+---------+---------+------+-------------------------+------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
+-------+------+-------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------------------------------------------------------+
| Note | 1105 | unique index idx_b of t is selected since the path only has point ranges with double scan |
+-------+------+-------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
スカイライン剪定
スカイラインプルーニングは、インデックスのヒューリスティックフィルタリングルールであり、誤った推定によって誤ったインデックスが選択される可能性を減らすことができます。インデックスを判断するには、次の3つの次元が必要です。
インデックス付きの列でカバーされるアクセス条件の数。 「アクセス条件」は、列範囲に変換できるwhere条件です。また、インデックス付きの列セットがカバーするアクセス条件が多ければ多いほど、この次元でのパフォーマンスは向上します。
テーブルにアクセスするためにインデックスを選択するときにテーブルから行を取得する必要があるかどうか(つまり、インデックスによって生成されるプランはIndexReader演算子またはIndexLookupReader演算子です)。テーブルから行を取得しないインデックスは、取得するインデックスよりもこのディメンションに適しています。両方のインデックスでテーブルから行を取得するためにTiDBが必要な場合は、インデックス付きの列でカバーされるフィルタリング条件の数を比較します。フィルタリング条件とは、インデックスに基づいて判断できる
where
の条件を意味します。インデックスの列セットがより多くのアクセス条件をカバーする場合、テーブルから取得される行の数が少なくなり、このディメンションでのインデックスの品質が向上します。インデックスが特定の順序を満たすかどうかを選択します。インデックスの読み取りは特定の列セットの順序を保証できるため、クエリの順序を満たすインデックスは、このディメンションで満たさないインデックスよりも優れています。
上記のこれらの3つの次元では、インデックスidx_a
が3つの次元すべてでインデックスidx_b
よりも悪くなく、1つの次元でidx_b
よりも優れている場合は、 idx_a
が優先されます。 EXPLAIN FORMAT = 'verbose' ...
ステートメントを実行するときに、skyline-pruningが一部のインデックスを除外すると、TiDBは、skyline-pruningの除外後の残りのインデックスを一覧表示するNOTEレベルの警告を出力します。
次の例では、インデックスidx_b
とidx_e
はどちらもidx_b_c
より劣っているため、スカイラインプルーニングによって除外されます。返される結果SHOW WARNING
は、スカイラインプルーニング後の残りのインデックスを表示します。
mysql> CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, d INT, e INT, INDEX idx_b(b), INDEX idx_b_c(b, c), INDEX idx_e(e));
Query OK, 0 rows affected (0.01 sec)
mysql> EXPLAIN FORMAT = 'verbose' SELECT * FROM t WHERE b = 2 AND c > 4;
+-------------------------------+---------+---------+-----------+------------------------------+----------------------------------------------------+
| id | estRows | estCost | task | access object | operator info |
+-------------------------------+---------+---------+-----------+------------------------------+----------------------------------------------------+
| IndexLookUp_10 | 33.33 | 738.29 | root | | |
| ├─IndexRangeScan_8(Build) | 33.33 | 2370.00 | cop[tikv] | table:t, index:idx_b_c(b, c) | range:(2 4,2 +inf], keep order:false, stats:pseudo |
| └─TableRowIDScan_9(Probe) | 33.33 | 2370.00 | cop[tikv] | table:t | keep order:false, stats:pseudo |
+-------------------------------+---------+---------+-----------+------------------------------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
+-------+------+------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------------------------------------------------+
| Note | 1105 | [t,idx_b_c] remain after pruning paths for t given Prop{SortItems: [], TaskTp: rootTask} |
+-------+------+------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
コスト見積もりベースの選択
スカイラインプルーニングルールを使用して不適切なインデックスを除外した後、インデックスの選択は完全にコスト見積もりに基づいています。テーブルへのアクセスのコスト見積もりには、次の考慮事項が必要です。
- ストレージエンジンのインデックス付きデータの各行の平均の長さ。
- インデックスによって生成されたクエリ範囲の行数。
- テーブルから行を取得するためのコスト。
- クエリの実行中にインデックスによって生成された範囲の数。
これらの要因とコストモデルに従って、オプティマイザはテーブルにアクセスするためのコストが最も低いインデックスを選択します。
コスト見積もりベースの選択に関する一般的なチューニングの問題
推定行数は正確ではありませんか?
これは通常、古い統計または不正確な統計が原因です。
analyze table
ステートメントを再実行するか、analyze table
ステートメントのパラメーターを変更できます。統計は正確で、TiFlashからの読み取りは高速ですが、オプティマイザーがTiKVからの読み取りを選択するのはなぜですか?
現在、TiFlashとTiKVを区別するためのコストモデルはまだ大まかなものです。
tidb_opt_seek_factor
つのパラメーターの値を減らすことができ、オプティマイザーはTiFlashを選択することを選択します。統計は正確です。インデックスAはテーブルから行を取得する必要がありますが、実際には、テーブルから行を取得しないインデックスBよりも高速に実行されます。オプティマイザがインデックスBを選択するのはなぜですか?
この場合、コスト見積もりはテーブルから行を取得するには大きすぎる可能性があります。
tidb_opt_network_factor
パラメーターの値を減らして、テーブルから行を取得するコストを減らすことができます。
コントロールインデックスの選択
インデックスの選択は、 オプティマイザーのヒントを介した単一のクエリで制御できます。
USE_INDEX
は、オプティマイザに特定のインデックスを使用するIGNORE_INDEX
使用しないように強制することができます。READ_FROM_STORAGE
を指定すると、オプティマイザは、クエリを実行するために特定のテーブルに対してTiKV/TiFlashストレージエンジンを選択するように強制できます。