データ構造:Mysqlインデックスの原理(わかりやすい)

序文

仕事でビジネスコードを書くことが多い場合、はっきり言ってCURDです。例えば、クエリ関数を実行するには、フロントエンドのパラメータをバックエンドに渡し、SQLクエリのデータをつなぎ合わせます。ユーザー エクスペリエンスを考慮すると、クエリ速度はできるだけ速くなければなりません。これには、クエリを高速化するために SQL を調整できる必要があります。

データは永続化する必要があり、データ量が膨大であるため、データはハードディスクにのみ保存できます。クエリを実行するときは、CPU の効率を考慮して、まずハードディスクからメモリにデータをロードする必要があります。ハードディスクからのデータ取得よりもメモリからのデータ取得の方が効率が高く、データ取得効率が非常に高いです。データをクエリする場合、通常、現在のデータを取得し、クエリ条件が満たされているかどうかを比較する必要があります。このプロセスはメモリ上で実行されるため、非常に高速です。データを比較する場合、データをディスクからロードする必要があります。メモリが最初です。これは通常 IO と呼ばれるものです。IO の消費時間は比較的長くなります。基本的に、クエリ プロセス全体における IO の割合は、時間のかかるデータ比較よりもはるかに高いため、次の方法を見つける必要があります。 IO の数を減らし、クエリの効率を向上させます。

ここに画像の説明を挿入

毎回ロードされるデータのサイズが 16K、テーブル データの各行のサイズが 1K、つまり 1 回の IO で 16 個のデータしかロードできないと仮定すると、大きなテーブル。次に、データの各行に一意の ID 列を作成し、キーと値のコンテナーを外部に作成できます。キーは ID 列の値、値は対応するデータ行のデータ アドレスです。各キーと値のデータ サイズは確実です。データの各行のサイズよりもはるかに小さいため、1 回の IO で大量のキーと値をメモリにロードし、比較して、条件を満たす key-value を取得し、 value に格納されているアドレスを介してテーブルにデータをロードすることで効率が大幅に向上します。インデックスはこの原理です。

文章

インデックスによってクエリ効率を向上できることがわかったところで、インデックスをどのように設計すればよいでしょうか?

インデックス構造 - 配列

データは間違いなく最も単純な構造です。Entry<key, value> 配列を初期化します。データを挿入した後、キーが行データ、値が行データの空間アドレスであるすべての列の値を維持します。

ここに画像の説明を挿入

順序なし配列

配列内のデータがキー値に従ってソートおよび挿入されない場合、各挿入は容量値の位置に挿入するだけで済み、より効率的です。データを削除するときに、スペースの断片化の問題が発生しない場合は、考慮すると、削除してもよい 削除位置は null で、削除効率は比較的高い; 挿入と削除の効率は高いが、クエリ効率は非常に低い 順序付けされていないため、配列が必要であることを意味します適切なデータ位置が見つかるまで最初から走査する必要があり、時間計算量は O(N) であるため、インデックス値が 100 万個ある場合、そのような効率性は明らかに要件を満たせません。順序なしは範囲クエリには適していません。配列全体を走査する必要があります。
ここに画像の説明を挿入

順序付けられた配列

順序なしデータクエリの時間計算量は O(N) であり、大量のデータの要件を満たすことができません データをキーの順序で挿入すると順序付き配列になります 順序付きクエリには二分探索が使用できます配列クエリ方法では、時間計算量が O(logN) であるため、クエリ効率が比較的高くなります。順序付けされているため、範囲クエリ効率が比較的高く、配列全体を走査する必要がありません。ただし、順序付けされた配列でデータを挿入する場合、配列の移動が発生するため、データ量が多いシナリオでは、挿入されたデータの位置が比較的前にあるため、後続のデータを後方に移動する必要があり、データ挿入にかかる時間は比較的長く、長い間、インデックス作成用のデータ構造としては明らかに適していません。
ここに画像の説明を挿入

配列はまず領域を空ける必要があるため、長さを指定する必要がありますが、データ領域がいっぱいになった場合の拡張の問題も考慮する必要があります。明らかに、データを大きなデータ構造として使用するのは適していません。データシナリオ。

インデックスのデータ構造 - ハッシュ

配列の挿入には空間の移動が含まれるため、挿入効率は低くなりますが、キーをハッシュ化し、その後に配列長の剰余を付加すると、配列内のキーの添字を計算でき、配列の時間計算量が計算されます。クエリは O(1) です。追加、削除、変更、確認が非常に高速なようなので、インデックスのデータ構造としてハッシュを選択します。

ここに画像の説明を挿入

あまり喜んではいけません。最適なハッシュ アルゴリズムであっても競合する可能性があります。つまり、異なるキーによって計算された添字アドレスが同じになる可能性があります。

ハッシュの競合を解決するには多くの方法があります。適切な方法があるかどうかを分析してみましょう。

ハッシュ競合チェーンのアドレス指定方法

各インデックス ノードの隣にフィールドを追加して、次のインデックス ノードのアドレスを保存します。配列内に既に存在するデータが計算されると、挿入ノードを格納するために新しいスペースが開かれ、そのスペース アドレスが末尾ノードに割り当てられます。配列の次のフィールドに対応するリンク リストの、このようにしてハッシュの競合を解決した後、クエリ時の式で配列の添字を計算し、現在の配列の添字ノード (ヘッド ノード) を取り出し、リンク リストはキー値が一貫しているかどうか、クエリ値が要件を満たしているかどうかを比較するために 1 つずつデータを調べます。この構造内のデータの量が多い場合、ハッシュの競合が非常に深刻になる可能性があり、その結果、リンクされたリストが非常に長くなり、クエリ走査の損失時間が非常に長くなります。

データ量が多い場合、リンクリストが長いため、各 IO で一定量のデータしかメモリにロードされず、量が多いと IO 頻度が高くなり、時間が長くなります。HashMap は 1.8 で赤黒ツリーを導入しました。これは、リンクされたリストが長すぎる場合のクエリ効率の低下の問題を最適化するために使用されます。

HashMapやRedisはこのデータ構造方式を採用しており、ハードディスク上に保存されるデータベースのインデックスファイルとは異なり、データがメモリ上に保存されるため、ハードディスクからデータを読み込むIOオーバーヘッドが節約されるとのことです。

ここに画像の説明を挿入

ハッシュの衝突 - 再ハッシュ

添え字がハッシュ式で計算され、ハッシュの競合が発生した場合は、新しいハッシュ式または元の式を使用して、計算された添え字の位置が空になるまでハッシュ値をハッシュし、保存します。値を取得する場合、式によって添字が計算された後、キーが一貫しているかどうかを比較し、次に添字のハッシュ計算を実行して値を比較するなど、新しい値がクエリされるまで繰り返されます。ハッシュ メソッドでは複数の計算が必要になる場合があります。数式計算なので時間がかかります。

ここに画像の説明を挿入

ハッシュの競合 - オープンアドレス方式

ハッシュの競合が発生した場合は、現在の添字をたどって検索し、アドレスが空の添字を見つけて保存します。クエリを実行する場合、ハッシュ式で添字を計算し、Key 値が一致するか比較します。一致しない場合は逆方向に検索します。トラバースした位置の添字が空の場合、クエリは終了します。現在の配列には適格なデータがありません。 . 検索は空のデータに遭遇すると終了するため、データを削除する場合、添え字データをクリアすることはできません (フィールドのみを使用してマークすることができます)。そうしないと、その背後にある同義データ (ハッシュ競合内のどのノード データか) が消去されます。は見つかりません。ハッシュの競合を減らすためには、フィルファクタ(現在の保存容量/容量の全長)をできるだけ小さくする必要がありますが、データ量が多い場合、フィルファクタが非常に低くなります。係数は小さく保たれます。

ThreadLocal の内部データ構造は、オープン アドレス方式を使用してハッシュの競合を解決します。

ここに画像の説明を挿入

ハッシュ メソッドは範囲検索には適していません。データ コンテナ全体をスキャンして適格なものを除外することしかできません。範囲検索のないインデックス キーの場合、ハッシュ メソッドは基礎となるデータ構造として使用できます。

インデックスデータ構造 - ツリー

ハッシュはインデックスをサポートしていないため、データ構造として Tree を使用することを検討できます。

二分木

インデックスデータ構造としてバイナリツリーを使用し、挿入する際にキーの大きさを比較し、キーが現在のノードより大きい場合は右に挿入し、現在のノードのキーより小さい場合は右に挿入します。それを左に。クエリを実行するとき、キー値が等しいかどうかも比較し、等しくない場合は、サイズを比較してクエリの方向を解決します。バイナリ ツリーを使用したクエリ効率は比較的速く、時間計算量は O です。 (logn). クエリ効率が高く、範囲クエリをサポートしています。

ここに画像の説明を挿入

バイナリ ツリーはクエリ効率が高く、範囲クエリをサポートしていますが、10、20、30、40、50 を順番に挿入するなど、極端な場合には連鎖するため、この場合のクエリ時間の複雑さは O(N) になります。データ量が多い場合には表示を受け付けられません。
ここに画像の説明を挿入

バランスの取れた二分木

二分木連鎖の状況を解決するために、左右の部分木の高さの差が 1 を超えてはいけないという制限を追加しました。それを超えると、左巻きと右巻きでバランスが保たれます。右側は比較的バランスが取れているため、バランスの取れたツリーと呼ばれます。

オンライン デモのアドレス: https://www.cs.usfca.edu/~galles/visualization/AVLtree.html

30を入れた後、10の左右の高さの差が2なので左回転が必要になります

ここに画像の説明を挿入

10を入れた後、30の左右の高さの差が2なので右回転する必要があります

ここに画像の説明を挿入

バランスの取れたバイナリ ツリーは、回転によって極端な場合の連鎖シナリオを回避しますが、大量のデータの場合、バランスを維持するために各挿入で左または右の回転が複数回発生する可能性があるため、挿入効率が非常に低く、明らかにバランスと補間が行われます。多数のシナリオには適していません。

赤黒い木

バランスの取れた二分木データが多い挿入データは、複数の左手系または右手系でバランスをとる必要があり、効率が悪いのですが、左手系または右手系の数を減らす方法はありますか?

赤黒ツリーはバランス バイナリ ツリーの拡張バージョンと考えることができます。回転数を減らすためにカラー マーキングが導入されています。著者は以前に赤黒ツリーに関する特別な記事を書きました。詳しく知りたい場合は、赤黒ツリーについては、「HashMap ソースコード学習:赤黒ツリーの原理を詳しく解説」をご覧ください。

赤黒木の特徴

赤黒木のオンライン デモンストレーション アドレス: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

  • ルート ノードは黒のノードである必要があります。
  • ノードは赤または黒です。
  • 葉っぱは全部黒いです。(葉はNullノードです)
  • 各赤いノードの子は両方とも黒です。(各リーフからルートまでのすべてのパス上に 2 つの連続する赤いノードがあってはなりません)
  • 任意のノードからその各リーフへのすべてのパスには、同じ数の黒いノードが含まれます。

赤黒の木が自転をどのように減らすか

赤黒の木の左側の構造は、バランスを維持するために色を変えるだけで済みます。

ここに画像の説明を挿入

バランスのとれたバイナリ ツリーの左側のバランスを維持するには、35 を回転ノードとして左に回転し、次に 50 を回転ノードとして右に回転する必要があるため、2 回の回転が必要です。

ここに画像の説明を挿入

赤黒ツリーの色の変更には、フィールドの状態値を変更するだけでよく、複数のポインタの変更が含まれず、回転が減少するため、挿入効率はバランスのとれたバイナリ ツリーよりも高くなります。

さて、赤黒ツリーの挿入効率が大幅に向上したのでクエリ効率を見てみましょう データ量が多い場合、各ノードの枝は2本しかないため、ツリーの高さは非常に大きくなりますディスクからレイヤーをロードするたびに、レイヤーが合計 30 ある場合、30 回の IO が必要となり、クエリの IO に多くの時間がかかります。これは明らかにインデックス データとしては適していません。大量のデータの下の構造。

B ツリー

赤黒ツリーには、大量のデータの場合に明らかな欠点があります。端的に言えば、分岐が少なく、各クエリのスコープが大きくなります。分岐の数が増加すると、スコープが縮小され、木の高さも低くなります。マルチウェイ ツリーは、各ノードにさらに多くの分岐を持たせ、クエリの範囲を狭めることを目的としています。

B-tree オンライン デモのアドレス: https://www.cs.usfca.edu/~galles/visualization/BTree.html

ここに画像の説明を挿入

B ツリーもマルチウェイ ツリーの実装であり、B ツリー内の各ノードはインデックス値と他のノードの参照アドレスを格納するだけでなく、データも格納します。B ツリーの各ノードに格納される子ノードの数は、各子ノードのサイズに依存します。たとえば、16K のノードがロードされるたびに、各子ノードは 4K (インデックス値、データ、参照アドレスなど) を占有します。 )、つまり、各ノードは 4 つの子ノードのみを保持できます。

クエリの際、子ノードに格納されているデータが大きすぎると IO の数が増加します。たとえば、ページがメモリに 16K ロードされるたびに、各ノードは 2K を占有し、最終的には 8 ノードしかロードできなくなります。値が低いほど、毎回より多くのノード ツリーがメモリにロードされ、メモリ内の計算にかかる時間はデータをロードする IO 時間よりも確実に短くなります。 Mysql の行の場合、B-tree を使用してクエリを表示すると効率が悪くなります。

B+ ツリー

B-treeのデメリットは、ノードファイルが大きすぎるためディスクの読み込み回数が増加し、クエリ効率が低下することです。ノードが実際のデータを保存しない場合、そのサイズは非常に小さくなります。B+ ツリーはこのシナリオを組み合わせてデータをリーフ ノードに配置しますが、非リーフ ノードはインデックス値のみを保存し、他のノード参照値ははい、これによりディスクの読み取り回数を大幅に減らすことができます。リーフ ノードはポインタを介してチェーンされ、この構造は範囲クエリをサポートします。B+ ツリーは大量のデータを含むシナリオでクエリ効率が比較的高いため、Mysql が B+ ツリーを使用します。

B+ ツリーのオンライン デモのアドレス: https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

ここに画像の説明を挿入

MySQLインデックス

プライマリインデックス

Mysql の InnoDB ストレージ エンジンは、主キーをクラスター化インデックスとして使用します。主キーがない場合は、空でない一意のインデックスをクラスター化インデックスとして使用します。存在しない場合は、暗黙的に自動インクリメント ID を作成します。クラスター化インデックス。クラスター化インデックスは、インデックス値と実際の行データ値を格納するデータ構造として B+ ツリーを使用する第 1 レベルのインデックスです。

既存のテーブル データ (ID が主キー):
ここに画像の説明を挿入

プライマリインデックスが確立された後、次のようになります。

ここに画像の説明を挿入

IDを検索条件として使用すると、インデックスに移動してクエリデータを迅速に取得します。

select * from student stu where stu.id=2; 

セカンダリインデックス

セカンダリ インデックスには完全な行データは格納されず、インデックス値とプライマリ インデックスの値のみが格納されます。

以下は、age フィールドを含む共通のインデックスを作成します。

ここに画像の説明を挿入

クエリ文が実行されると、年齢インデックスに基づいて第 1 レベルのインデックスの値が検索されます。行全体のデータが返されるため、第 2 レベルのインデックスで値をクエリした後、その値が返されます。第 1 レベルのインデックスの一部がクエリ用のテーブル、つまりデータの行全体をクエリする第 1 レベルのインデックス ツリーに返されます。次に、返されるデータ値がセカンダリ インデックス ツリーに存在する場合は、テーブルを返しません。

select * from student stu where stu.age=12; 

要約する

さまざまなシナリオにはさまざまなデータ構造が適しており、具体的な構造の選択は、データ量、フィールド タイプ、クエリ頻度に応じて決定する必要があります。

おすすめ

転載: blog.csdn.net/weixin_45031612/article/details/131465223