Mysql インデックスの原理と特性を理解する | JD Logistics 技術チーム

開発者として、実行に時間がかかる SQL に遭遇すると、基本的に誰もが「インデックスを追加する」と言うでしょう。しかし、インデックスとは何で、その特徴は何でしょうか? 簡単に説明しましょう。

1 インデックスの仕組みとクエリを高速化する方法

インデックスは本の目次のようなもので、データベース テーブルへのデータ アクセスの速度を向上させるデータベース オブジェクトです。リクエストが届いたとき、ディレクトリがあれば、すぐにチャプタを見つけて、そのチャプタからデータを見つけます。カタログがなければ、干し草の山から針を見つけるようなもので、明らかに困難です。これは、私たちがよく遭遇する原因であるフルテーブルスキャンです。

インデックス レコードに含まれる基本情報には、キー値 (つまり、インデックスを定義するときに指定したすべてのフィールドの値) + 論理ポインタ (データ ページまたは別のインデックス ページを指す) が含まれます。通常、インデックス レコードにはインデックス フィールド値 (および 4 ~ 9 バイトのポインター) のみが含まれるため、インデックス エンティティは実際のデータ行よりもはるかに小さく、インデックス ページはデータ ページよりも密度が高くなります。インデックス ページには、より多くのインデックス レコードを保存できます。これは、インデックス内で検索する際の I/O に大きな利点があることを意味します。これを理解することは、インデックスを使用する利点を本質的に理解するのに役立ちます。これは、パフォーマンスの最適化のほとんどでもあります。ポイントを入力する必要があります。

1) インデックスなしでデータにアクセスします。

2) バランスの取れたバイナリ ツリー構造インデックスを使用してデータにアクセスします。

最初の画像はインデックスを使用していません。順次検索を実行し、データの順序に従って 1 つずつ照合します。必要なデータを見つけるまでに 5 回の検索が必要です。2 番目の画像は、単純なバランス バイナリ ツリー インデックスを使用しています。つまり、使用するのは 3 回だけです。これはデータ量が少ない場合であり、データ量が多い場合に効果がより顕著になります。つまり、インデックスを作成する目的は、データ検索を高速化することです。

2 インデックスの構成要素と種類

インデックスを実装するには、ハッシュ、配列、ツリーなど、多くの一般的な方法がありますが、以下では、これらのモデルの使用における違いを紹介します。

2.1 ハッシュ

ハッシュの考え方はシンプルで、ハッシュ関数アルゴリズムを使用して、挿入したキーの対応する値を計算することです (以前は、ハッシュマップ、シフト XOR などの計算方法と同じように、通常は剰余が取られていました) .)、この値をある位置に置きます。この位置はハッシュ スロットと呼ばれます。対応するディスク位置ポインタがハッシュ スロットに配置されます。一言で要約すると、ハッシュ インデックスには、インデックス フィールドのハッシュ値と、データが配置されているディスク ファイル ポインタが格納されます。

しかし、どのようなアルゴリズムであっても、データ量が多い場合には、必ず異なるデータが同じハッシュスロットに配置されることは避けられません。例えば、辞書に載っている「呉」と「武」は同じ発音ですが、辞書を引くときは下からしか調べることができません。インデックス処理についても同様で、必要に応じてリンク リストが取り出され、順次走査されます。

  • 欠点: 順序付けされていないインデックス、間隔クエリでは複数のディスク アクセスが発生し、時間のかかる複数の IO が受け入れられにくいため、間隔クエリのパフォーマンスが低くなります。
  • 利点: 挿入は高速で、後で追加するだけで済みます。
  • シナリオ: memcached などの同等のクエリ。ハッシュの競合を避けるため、大量の繰り返しデータが含まれる列には適していません。
  • 概要: Java ハッシュマップとして考えてください。

2.2 順序付き配列

間隔クエリが必要な場合、ハッシュ インデックスのパフォーマンスは満足のいくものではありません。このとき、順序付けされた配列の利点を反映することができます。

順序付けされた配列から A と B の間の値を取得する必要がある場合は、二分法によって A の位置を特定するだけでよく、時間計算量は O(log(N)) で、A から B まで移動します。速度に関しては基本的には最速と言えます。しかし、更新する必要がある場合は、実行する必要のある操作がたくさんあります。データの一部を挿入する必要がある場合は、そのデータの後にすべてのデータを移動する必要があり、パフォーマンスが無駄になります。要約すると、順序付けられた配列構造へのインデックス付けに適しているのは、あまり変化しないデータだけです。

  • 欠点: 新しいデータを挿入する場合、後続のデータをすべて変更する必要があるため、コストが若干高くなります。
  • 利点: クエリ速度は非常に速く、理論上の最大値です。
  • シナリオ: ほとんど変更されないアーカイブ クエリ、ログ クエリなど
  • 概要: 順番に並べた配列です

2.3 二分探索木

基本原則は、ツリーの左側のノードは親ノードより小さく、右側のノードは親ノードより大きいということです。

ここで、二分探索木のクエリ効率は原則として O(log(N)) であることがわかりますが、バランスのとれた二分木を保証するために、更新効率も O(log(N)) になります。ただし、データが大量になるとツリーの高さが非常に高くなるため、ディスクに何度もアクセスすることはお勧めできません。極端な場合には、ツリーはリンク リストに縮退し、クエリの複雑さは O(n) に減少します。

マルチフォーク ツリーに発展すると、つまり複数の子ノードがある場合、ツリーの高さが大幅に減り、ディスクへのアクセスが減少します。

  • 欠点: データの量が多い場合、ツリーが高くなりすぎ、複数のディスク アクセスが発生します。
  • 利点: マルチフォーク ツリーに進化すると、ツリーの高さとディスク アクセスの数が減ります。
  • シナリオ: 多くのシナリオに適用可能
  • 要約: その木は左側は小さく、右側は大きいです

2.4 B ツリー

各ノードに複数の要素を格納し、各ノードにできるだけ多くのデータを格納します。各ノードには 1000 個のインデックス (16k/16=1000) を格納できるため、バイナリ ツリーはマルチフォーク ツリーに変換され、ツリーのフォーク ツリーを増やすことで、高くて細いツリーから短くて太いツリーに変化します。 100 万個のデータを構築するには、ツリーの高さは 2 レベル (1000*1000=100 万) だけ必要です。つまり、データのクエリに必要なディスク IO は 2 回だけです。ディスク IO の数が削減され、データのクエリの効率が向上します。

このデータ構造は B ツリーと呼ばれ、B ツリーは多分岐平衡探索木です。

2.5B+ ツリー

B+ ツリーと B ツリーの主な違いは、非リーフ ノードにデータが格納されるかどうかです。

  • B ツリー: 非リーフ ノードとリーフ ノードの両方にデータが格納されます。
  • B+ ツリー: リーフ ノードのみがデータを保存し、非リーフ ノードはキー値を保存します。リーフ ノードは双方向ポインタを使用して接続され、最下位のリーフ ノードは双方向の順序付きリンク リストを形成します。

B+ ツリーのリーフ ノードはリンク リストを介して接続されているため、下限を見つけた後、間隔クエリを迅速に実行できます。これは、通常の順序トラバーサルよりも高速です。

3 インデックスのメンテナンス

データを挿入するとき、インデックスはデータの順序を保証するために必要な操作を実行する必要があります。通常、自己増加するデータは最後に直接追加できますが、特殊な場合にデータを途中で追加すると、後続のデータをすべて移動する必要があり、効率に影響します。

最悪の場合、現在のデータ ページ (ページは MySQL ストレージの最小単位) がいっぱいになった場合、新しいデータ ページを申請する必要があります。このプロセスはページ分割と呼ばれます。ページ分割が発生すると、パフォーマンスに影響します。ただし、MySQL は無知なデータ分割ではないため、データを途中から分割すると、自動インクリメントされる主キーのためにパフォーマンスの半分が無駄になります。 MySQL は、インデックスのタイプと挿入されたデータの追跡に基づいて分割方法を決定します。通常、MySQL データ ページの先頭に格納されます。分散挿入の場合は、中央で分割されます。連続して挿入される場合は、通常、分割を開始する挿入ポイント、または挿入ポイントの数行後に選択したことが原因で発生します。途中で分割するか、最後で分割するかを決めます。

不規則なデータが挿入され、後の値が前の値より大きいという保証がない場合、上記の分割ロジックがトリガーされ、最終的に次の効果が達成されます。

したがって、ビジネスで主キーをカスタマイズする必要がない限り、ほとんどの場合、自動増加インデックスを使用する必要があります。インデックスが 1 つだけ存在し、そのインデックスが一意であることを確認することが最善です。これにより、テーブルのバックが回避され、クエリで 2 つのツリーが検索されます。データ ページの順序性を確保し、インデックスをより有効に活用します。

4 返信

平たく言えば、インデックス列が select で必要な列にある場合 (mysql のインデックスはインデックス列の値に従ってソートされるため、列の一部の値はインデックス ノードに存在します)、またはインデックスに基づいている場合レコードを取得できればテーブルを返す必要はありません。select で必要な列にインデックス以外の列が多数ある場合、インデックスは最初に主キーを見つけてから、対応するキーを見つける必要があります。テーブル内の列情報。テーブル リターンと呼ばれます。

テーブル バッキングを導入するには、当然、クラスター化インデックスと非クラスター化インデックスを導入する必要があります。
InnoDB クラスター化インデックスのリーフ ノードには、行レコードが格納されます。したがって、InnoDB には、 1 つのクラスター化インデックス。 :

  • テーブルが主キーを定義している場合、PK はクラスター化インデックスです。
  • テーブルに主キーが定義されていない場合、最初の非 NULL 一意インデックス (NULL 一意ではない) 列はクラスター化インデックスになります。
  • それ以外の場合、InnoDB はクラスター化インデックスとして非表示の行 ID を作成します。

通常のインデックスクエリ方法を使用する場合、最初に通常のインデックスツリーを検索し、次に主キー ID を取得してから、ID インデックスツリーを再度検索する必要があります。非主キーインデックスのリーフノードには実際に主キーのIDが格納されているためです。この処理ではインデックスを使用しますが、実際には最下層で 2 つのインデックス クエリが実行され、この処理をテーブル リターンと呼びます。つまり、非主キー インデックスに基づくクエリでは、もう 1 つのインデックス ツリーをスキャンする必要があります。したがって、アプリケーションでは主キー クエリを使用するようにしてください。または、高頻度のリクエストがある場合は、テーブルの戻りを防ぐために合理的に結合インデックスを確立します。

5つのインデックスカバレッジ

一言で言えば、SQL で必要なすべての列データを 1 つのインデックス ツリーだけから取得でき、テーブルに戻る必要がなく、速度も速くなります。 SQL で実装されている場合、実行プランの出力結果の Extra フィールドがインデックスを使用している限り、インデックス カバレッジをトリガーできます。

一般的な最適化方法は、すべてのクエリ フィールドをインデックスに組み込むという上記の方法ですが、DBA がそれを構築させてくれるかどうかについては、あなた自身が戦う必要があります。

一般的なインデックス カバレッジに適用できるシナリオには、全テーブル数クエリの最適化、列クエリ テーブルの戻り、およびページング テーブルの戻りが含まれます。 mysql の上位バージョンは最適化されており、ジョイント インデックスのフィールドの 1 つがヒットし、もう 1 つが id の場合、テーブルを返さずに自動的に最適化されます。主キーはセカンダリ インデックスのリーフに格納されるため、インデックス カバレッジともみなされ、追加コストは必要ありません。

6 左端の一致原則

簡単に言うと、「xx%」を使用すると、条件が満たされていればインデックスも使用されます。
結合インデックスの場合、(a, b) の結合インデックスを作成する例を示します。

a の値は 1、1、2、2、3、3 の順序であり、b の値は順不同で 1、2、1、4、1、2 であることがわかります。しかし、a の値が等しい場合、b の値は順番に配置されますが、この順序は相対的なものであることもわかります。これは、結合インデックスを作成するための MySQL のルールが、まず最初のフィールドの並べ替えに基づいて結合インデックスの左端のフィールドを並べ替え、次に 2 番目のフィールドを並べ替えることであるためです。したがって、b=2 などのクエリ条件にインデックスを使用する方法はありません。たとえば、
KEY(,) USING というインデックスを作成してみましょう。 BTREE 最初の SQL、フル テーブル スキャンを実行します idx_time_zone time_zonetime_string

2番目のSQLを実行すると、インデックスが使用されていることがわかります。

もう一度 2 つの SQL を見てみましょう。作成されたインデックスは KEY(,) USING BTREE idx_time_zone time_zonetime_string

通常のロジックでは、2 番目の SQL はインデックス フィールドの順序に従っていないため、インデックスは使用されないはずですが、実際の状況は予想と異なります。これはなぜでしょうか?

mysql が Oracle に買収されて以来、mysql には Oracle の以前のテクノロジーの多くが組み込まれており、mysql の上位バージョンでは where 条件の順序が自動的に最適化されます。簡単に言うと、クエリ オプティマイザーがこのステップを実行し、SQL が前処理を実行し、より良いクエリのためにどのルールが使用されるかを決定します。

ところで、mysql のクエリ オプティマイザーが役立つことをいくつか挙げておきます。

6.1 条件変換

たとえば、a=b かつ b=2 の場合、a=2 の条件付き転送を取得できます。最後の SQL は a=2 と b=2 > < = のように渡すことができます

6.2 無効なコードの除外

たとえば、1=1 と a=2 の場合、1=1 は常に正しいため、最終的には a=2 に最適化されます。
たとえば、1 =0 は常に false です。これも除外されます。SQL 全体が無効です。
、または a が null の非 null フィールドも除外されます。

6.3 事前に計算する

算術演算を含む部分 (a= 1+2 の場合、a=3 の場合など) は計算に役立ちます。

6.4 アクセスの種類

条件式を評価するとき、MySQL は式のアクセス タイプを決定します。以下に、いくつかのアクセス タイプを最良から最悪の順に示します。

  • system システムテーブルであり、定数テーブルです
  • const 定数テーブル
  • eq_ref の一意/プライマリ インデックス。アクセスには「=」を使用します。
  • 参照インデックスはアクセスに「=」を使用します
  • ref_or_null インデックスはアクセスに「=」を使用し、NULL になる可能性があります
  • 範囲インデックスはアクセスに BETWEEN、IN、>=、LIKE などを使用します。
  • インデックス インデックス フルスキャン
  • ALLテーブルフルスキャン

実行計画をよく見ると、それが何を意味するのかが一目でわかります。

ここで、index_col=2 およびnormal_col =3 の場合、index_col=2 がドライバー項目として選択されます。ドライバー項目の意味は、SQL がその実行プランを選択するときに、複数の実行パスが存在する可能性があるということです。1 つはテーブル全体のスキャンで、次にフィルターされて、インデックス フィールドの値と一致するかどうかが確認されます。インデックスフィールド。もう 1 つは、インデックス フィールド (キー値 = 2) を通じて対応するインデックス ツリーを検索し、結果をフィルター処理して、それが非インデックス フィールドの値と一致するかどうかを比較することです。通常の状況では、インデックス作成に必要なディスク読み取りはテーブル全体のスキャンより少ないため、より優れた実行パスと呼ばれます。つまり、インデックス フィールドを駆動式として使用します。

6.5 範囲アクセス

簡単に言えば、a in(1,2,3) は a=1 または a=2 または a=3 と同じであり、1 と 2 の間も a>1 および a<2 と同じです。最適化するために。

6.6 インデックスアクセスタイプ

同じプレフィックスを持つインデックスの使用は避けてください。つまり、フィールドが複数のインデックスで同じプレフィックスを持つべきではありません。たとえば、フィールドに一意のインデックスが確立されている場合、この時点で結合インデックスを作成すると、オプティマイザはどのインデックスを使用するかを認識できません。また、単一インデックスと同じ接頭辞を持つ結合インデックスを構築した場合、条件を記述しても結合インデックスが使用されない場合があります。もちろん強制することもできますが、それはまた別の話です。

6.7 変換

where -2 = a は自動的に where a= -2 になるなど、単純な式は変換できますが、数学的演算が含まれる場合は変換できません。たとえば where 2= -a は where a at に自動的に変換されません。今回は =-2。

2 番目の SQL ではインデックスを使用できます

そのため、開発時にはSQLの書き方に注意し、a=-2の箇所を意識して書く必要があります。

6.8 and、union、order by、group byなど

1)そして

and 条件の後、インデックスがない場合はテーブル全体をスキャンします。より適切なアクセス タイプがあります。5.4 を参照してください。より適切なストレージ タイプのインデックスが使用されます。同じ場合、どちらのインデックスが最初に作成され、どちらが使用されます。

2)組合

各共用体ステートメントは個別に最適化されます

ここでは、インデックスを使用して 2 つの SQL が別々に実行され、結果セットがマージされます。

3)注文方法

order by は、すでにインデックスが設定されているフィールドなど、無効な並べ替えを除外します。

2 番目の SQL のクエリ効果は最初の SQL と同じです

したがって、SQLを記述する際には、「xxx」で並べ替えるなど意味のない無駄なソートは記述しないでください。

4)グループ化

簡単に言うと、group by フィールドにインデックスがあれば、インデックスが作成されます。group by a order by a の場合、ここでの order by は、order by が書き込まれないことを意味します。結果セットはすでにソートされています。6.8-3 を参照してください。 order by
select unique col_a from table a は、col_a による group からの selectcol_a と同等です。

7 インデックスのプッシュダウン

主な核心点は、データ フィルタリング プロセスを、以前のようにフィルタリングのためにサーバー層に置くのではなく、ストレージ エンジン層に置いて処理することです。

名前と年齢の両方がテーブルにインデックス付けされている場合、クエリ条件は、名前が 'xx%' で、年齢 = 11 の場合です。mysql の以前のバージョン (5.6 より下) では、インデックスの左端の一致原則に従って、次の結果を得ることができます。放棄 年齢に応じて、名前に基づいてデータのみをフィルタリングします。名前に基づいてすべての ID を取得したら、ID に基づいてテーブルに戻ります。

mysql の上位バージョンでは、age 属性は無視されません。age 属性を使用してフィルタリングすると、年齢 11 のデータが直接フィルタリングされます。年齢に基づいてフィルタリングされていないデータ項目が 10 個あると仮定します。フィルタリング後は、残り 3 項目、つまり返信が 7 件減ります。 IO を削減すると、パフォーマンスの消費が大幅に削減されます

8 小さなテーブルが大きなテーブルを駆動する

「小さなテーブルが大きなテーブルを駆動する」という言葉をよく聞きますが、これは主に、小さなテーブルのデータ セットが大きなテーブルのデータ セットを駆動し、それによって接続数が減少することを意味します。例えば:

テーブル A には 10,000 のデータがあり、テーブル B には 1,000,000 のデータがあります。テーブル A が駆動テーブルとして使用され、ループの外層にある場合、必要な接続は 10,000 のみです。テーブル B が外層にある場合、100 万回ループする必要があります。

実際のテストを見て、mysql 5.7+の環境を準備してみましょう

2 つのテーブルを準備します。1 つは ib_asn_d データ 9175 を含むテーブル、もう 1 つは bs_itembase_ext_attr データ 1584115 を含むテーブルで、どちらも製品コード フィールドにインデックスがあります。

まず、小さなテーブルが大きなテーブルを駆動します

テストを複数回繰り返し、実行時間は約 7 秒です。
次に、小さなテーブルを駆動する大きなテーブルを見てみましょう。

は 300 秒近くあり、桁違いです。
次に、実行計画を個別に分析します。実行計画の最初の項目はドライバー テーブルです。

小さなテーブルは大きなテーブルを駆動し、大きなテーブルはインデックスを使用し、小さなテーブルはテーブル全体をスキャンし、8,000 行を超える行のみをスキャンします。

大きなテーブルは小さなテーブルを駆動し、大きなテーブルのフル テーブル スキャンには 1470,000 行のスキャンが必要です。
多くのテストを行った結果、次の結論に達しました。

  1. 左結合を使用する場合、左側のテーブルが駆動テーブル、右側のテーブルが駆動テーブルになります。
  2. 右結合を使用する場合、右側のテーブルが駆動テーブル、左側のテーブルが駆動テーブルになります。
  3. 内部結合を使用する場合、MySQL は比較的少量のデータを含むテーブルを駆動テーブルとして選択し、大きなテーブルを駆動テーブルとして選択します。
  4. ドライバ テーブル インデックスは有効になりませんが、非ドライブ テーブル インデックスは有効になります。

小さなテーブルが駆動テーブルであることを確認することが重要です。

9 まとめ

  1. カバーするインデックス: クエリ条件が通常のインデックス (またはジョイント インデックスの左端の主フィールド) を使用する場合、クエリ結果はジョイント インデックスのフィールドまたは主キーとなり、結果はテーブルの戻り操作なしで直接返されるため、クエリのコストが削減されます。 IO ディスクの読み取りと書き込み データの行全体のため、高頻度フィールドの結合インデックスを確立する必要がある
  2. 左端のプレフィックス: 共用体インデックスの左端の N フィールド、または文字列インデックスの左端の M 文字。インデックスを構築するときは、クエリ オプティマイザーがインデックスの使用方法を決定できなくなるのを防ぐために、左側のプレフィックスを繰り返さないように注意してください。
  3. インデックス プッシュダウン: 「hello%」のような名前と経過時間 > 10 の取得では、MySQL バージョン 5.6 より前では、一致するデータがクエリによってテーブルに戻されます。バージョン 5.6 以降では、最初に経過期間が 10 未満のデータが除外され、次にテーブル リターン クエリが実行されてテーブル リターン率が低下し、取得速度が向上します。

著者: JD Logistics Wu Siwei 
出典: JD Cloud Developer Community 転載する場合は出典を明記してください

IntelliJ IDEA 2023.3 と JetBrains の年次メジャー バージョン アップデート 新しいコンセプト「防御型プログラミング」: 安定した仕事に就く GitHub .com の稼働率が向上1,200 を超える MySQL ホストがある場合、8.0 にシームレスにアップグレードするにはどうすればよいですか? Stephen Chow の Web3 チームは来月、独立したアプリをリリースする予定 Firefox は廃止されるのでしょうか? Visual Studio Code 1.85 がリリース、フローティング ウィンドウ Yu Chengdong: ファーウェイは来年破壊的な製品を発売し、業界の歴史を書き換える 米国CISA C/C++ を放棄し、メモリ セキュリティの脆弱性を排除することを推奨します TIOBE 12 月: C# が今年のプログラミング言語になると予想されます Lei Jun の論文執筆30 年前: 「コンピュータウイルス判定エキスパートシステムの原則と設計」
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10320986