MySQL データベースのインデックス順序の詳細な説明

MySQL データベースのインデックス順序の詳細な説明

「並べ替え」という言葉を考えると、ほとんどのアプリに並べ替え場所があるという第一印象があります。Taobaoの商品は購入時間順に並べ替えられ、Bilibiliのコメントは人気順に並べ替えられます...

MySQL でのソートに関して、最初に思い浮かぶことは何でしょうか?キーワードの順序は?順序フィールドにインデックスを設定するのが最適ですか?リーフノードはすでに整っていますか?それとも、MySQL 内でのソートはできるだけ避けるべきでしょうか?

事件の原因

ここで、ユーザーの友人テーブルがあるとします。

テーブル `user` を作成します (
  `id` int(10) AUTO_INCREMENT,
  `user_id` int(10)、
  `friend_addr` varchar(1000)、
  `friend_name` varchar(100)、  
  主キー (`id`)、
  キー `user_id` (`user_id`)
)ENGINE=InnoDB;

現在、表には注意が必要な点が 2 つあります。

  • ユーザーのuser_id、友人の名前friend_name、友人のアドレスfriend_addr
  • user_idがインデックス化されている

ある日、Xiaoyuan というジュニア開発エンジニアが、Xiaowang というジュニアプロダクトマネージャーから次のようなリクエストを受けました。
シャオ・ワン: シャオユアン同志、バックグラウンドで機能を追加する必要があります。この機能は、ユーザー ID に従ってすべての友人の名前とアドレスを照会し、友人の名前を辞書に従って並べ替えることをサポートする必要があります。
小元:わかりました。この機能は簡単です。すぐにオンラインになります。

そこで、Xiaoyuan は次の SQL を記述しました。

user_id=? の user から friend_name,friend_addr を選択します。名前で並べ替えます。

あっという間に、Xiaoyuan は大々的な宣伝とともにオンラインになりました。すべて順調に進んでいたのですが、ある日、オペレーションのクラスメートが次のような質問をしました。

user_id=10086 の user から friend_name,friend_addr を選択し、名前で並び替えます

しかし、このクエリは通常よりもはるかに遅く、データベースは遅いクエリを報告しました。Xiaoyuan はパニックになりました。何が起こっているのですか? user_id にはインデックスがあり、select * の代わりに select friend_name, friend_addr のみを巧みに使用しました。この時、シャオユアンは自分を慰め続け、落ち着くように自分に言い聞かせていましたが、突然、explain コマンドがあることを思い出しました。彼は、explain を使用してその SQL の実行プランを確認することにしました。シャオユアンが explain を使用した後、彼は追加フィールドに危険そうな単語「using filesort」を見つけました。

「このクエリは実際には伝説的なファイルソートを使用していますが、友達があまりいない人の場合は、ファイルソートを使用しても高速になるはずです」と、user_id=10086に多くの友達がいない限り。その後、Xiaoyuanが確認したところ、このユーザーには実際には10万人以上の友達がいることがわかりました〜。

小さな猿は考え込んでいました。どうやら、この責任は私にあるようです。100,000 のデータ ポイントは多すぎます。また、filesort を使用したソートの原理とは何でしょうか?

解剖学ファイルの並べ替え

上記の問題は、10w のデータが大きすぎて、ソートされていなくても遅くなるということだと言う人がいるかもしれません。これは実際には理にかなっています。10w のデータを一度にチェックすると、MySQL メモリ バッファとネットワーク帯域幅の両方が非常に消費されます。1000 の制限を追加したらどうなるでしょうか。全体的なデータ パケット サイズが小さくなったため、ネットワーク帯域幅の問題は確実に解決されましたが、filesort の使用に関する問題は解決されていません。これを見ると、filesort を使用するとファイルがソートされるのかという疑問が湧くかもしれません。ファイル内ではどのように分類されますか?あるいは、こう尋ねてみましょう。ソートを設計するように依頼された場合、どのように対処しますか?これらの質問と考察を踏まえて、filesort の使用に伴う技術的な困難とその解決方法を見てみましょう。

  1. まず、user_id がインデックス化されているので、まずは user_id インデックス ツリー上の対象データ、つまり user_id=10086 のデータを検索します。ただし、friend_name フィールドと friend_addr フィールドをクエリする必要があります。残念ながら、user_id インデックスだけでは、これら 2 つのフィールドの値を見つけることはできません。
  2. したがって、テーブルに戻って、user_id に対応する主キーを通じて主キー インデックス ツリーを検索する必要があります。最初の user_id=10086 の friend_name フィールドと friend_addr フィールドが見つかりました。
  3. 今何をすればいいでしょうか? friend_name をソートする必要があるため、直接返すのは絶対に正しくありません。どのようにソートするのでしょうか?データはまだ見つかっていないので、まずは見つかったデータを sort_buffer という 1 か所に置く必要があります。名前から推測できると思います。そうです、sort_buffer はこの場合のソートに使用されるバッファです。ここで注意すべき点は、各スレッドに個別の sort_buffer があるということです。これを行う主な目的は、複数のスレッドが同じメモリ ブロックで動作することによって発生するロック競合を回避することです。
  4. 最初のデータの friend_name と friend_addr が sort_buffer に格納されても、もちろんまだ終了ではなく、user_id=10086 のすべての friend_name と friend_addr が sort_buffer に格納されるまで同期手順が繰り返されます。
  5. sort_buffer のデータがデータに入力されたので、次はそれをソートします。ここで、MySQL は friend_name に対してクイック ソートを実行します。クイック ソートの後、sort_buffer の friend_name は順序どおりになります。
  6. 最後に、sort_buffer の最初の 1000 項目が返され、プロセスが終了します。

すべてがスムーズに見えますが、sort_buffer はメモリ領域を占有するため、扱いにくいです。メモリ自体は無限ではなく、上限が確実にあります。もちろん、sort_buffer が小さすぎることはできません。小さすぎると、あまり意味がありません。 InnoDB ストレージ エンジンでは、この値はデフォルトで 256K になります。

mysql> 'sort_buffer_size' のような変数を表示します。
+------------------+--------+
| 変数名 | 値 |
+------------------+--------+
| ソートバッファサイズ | 262144 |
+------------------+--------+

つまり、sort_buffer に入れるデータが 256K より大きい場合、sort_buffer のクイック ソート メソッドは確実に機能しません。この時点で、MySQL はデータ サイズに応じて自動的に拡張できないのかと疑問に思うかもしれません。さて、MySQLはマルチスレッドモデルです。各スレッドが拡張されると、他の機能に割り当てられるバッファ(変更バッファなど)が小さくなり、他の機能の品質に影響を与えます。

このとき、ソート方法を変更する必要があります。はい、これは実際のファイル ソート、つまりディスク上の一時ファイルです。MySQL はマージ ソートの概念を使用して、ソートするデータをいくつかの部分に分割します。各データはメモリ内でソートされた後、一時ファイルに格納されます。最後に、これらのソートされた一時ファイルのデータはマージされ、再度ソートされます。これは典型的な分割統治の原則です。具体的な手順は次のとおりです。

  1. まず、ソートするデータを sort_buffer に入れることができる部分に分割します。
  2. sort_buffer 内の各データをソートし、ソート後に一時ファイルに書き込みます。
  3. すべてのデータが一時ファイルに書き込まれると、各一時ファイルは整列しますが、全体が整列しているわけではなく、全体が整列していないため、次にデータを結合する必要があります。
  4. tmpX と tmpY という 2 つの一時ファイルがあるとします。このとき、データの一部が tmpX からメモリに読み込まれ、次にデータの一部が tmpY からメモリに読み込まれます。なぜ全体や 1 つのファイルではなく一部なのか、不思議に思うかもしれません。まず、ディスクは遅いので、毎回できるだけ多くのデータをメモリに読み込むようにしてください。ただし、バッファ スペースの制限があるため、読み込みすぎないようにしてください。
  5. tmpXについては、読み込まれるのはtmpX[0-5]であると仮定し、tmpYについては、読み込まれるのはtmpY[0-5]であると仮定します。次に、次のように比較するだけです。tmpX[0] < tmpY[0]の場合、tmpX[0]が最小である必要があります。次に、tmpX[1]とtmpY[0]を比較します。tmpX[1] > tmpY[0]の場合、tmpY[0]が2番目に小さい必要があります。これらを1つずつ比較することで、最終的にtmpXとtmpYを順序付けられたファイルtmpZにマージできます。このようなtmpZファイルを複数再度マージできます。最終的に、すべてのデータを順序付けられた大きなファイルにマージできます。

ファイルのソートが非常に遅いのですが、他に解決策はありますか?

上記のソート処理を通じて、ソートするデータが非常に大きく、sort_buffer のサイズを超える場合は、ファイル ソートが必要であることがわかります。ファイル ソートにはバッチ ソートとマージが含まれ、非常に時間がかかります。この問題の根本的な原因は、sort_buffer が十分ではないことです。friend_name をソートする必要があることに気付いたかどうかはわかりませんが、friend_addr も sort_buffer に詰め込まれています。このように、1 行のデータのサイズは friend_name の長さ + friend_addr の長さに等しくなります。sort_buffer に friend_name フィールドのみを保存できますか? この方法では、全体的な使用スペースが大きくなり、一時ファイルが不要になる可能性があります。そうです、これは次に説明するもう 1 つのソート最適化、rowid ソートです。

rowidソートの考え方は、不要なデータをsort_bufferから除外し、必要なデータのみをsort_bufferに保持することです。では、必要なデータとは何だと思いますか? friend_name を入力するだけですか?これは絶対にうまくいきません。ソートが完了したら、friend_addr はどうなるのでしょうか?したがって、主キー ID も入力する必要があります。ソート後、ID を介してセカンダリ テーブルに戻り、friend_addr を取得できます。したがって、一般的なプロセスは次のようになります。

  1. user_idインデックスに従って対象データを検索し、テーブルに戻ってidとfriend_nameのみをsort_bufferに格納します。
  2. すべてのターゲットデータがsort_bufferに入るまでステップ1を繰り返します。
  3. sort_buffer内のデータをfriend_nameフィールドでソートする
  4. ソート後、id に従ってテーブルを再度検索して friend_addr を見つけ、1,000 件のレコードが返されるとプロセスが終了します。

実際にここで注意すべき点がいくつかあります。

  • この方法では、テーブルに 2 回戻る必要があります。
  • sort_buffer は小さくても、データの量が大きい場合は、一時ファイルをソートする必要があります。

そこで疑問になるのが、MySQL は 2 つの方法のどちらを選択すべきかということです。どちらの方法を使用するかは、特定の条件によって決まります。条件は、sort_buffer 内の単一行の長さです。長さが大きすぎる場合 (friend_name + friend_addr の長さ)、rowid が使用されます。それ以外の場合、最初の方法では、max_length_for_sort_data に基づく長さの標準が使用されます。これは、デフォルトで 1024 バイトです。

mysql> 'max_length_for_sort_data' のような変数を表示します。
+--------------------------+-------+
| 変数名 | 値 |
+--------------------------+-------+
| ソートデータの最大長 | 1024 |
+--------------------------+-------+

テーブルに戻って再度並べ替えたくない

実際、上記のどの方法を使用する場合でも、すべてテーブルに戻って並べ替える必要があります。テーブルに戻るのは、セカンダリ インデックスにターゲット フィールドがないためであり、並べ替えるのは、データが順序付けられていないためです。セカンダリ インデックスにターゲット フィールドがあり、すでに並べ替えられている場合は、両方の長所を兼ね備えた方法ではないでしょうか。

そうです、それはジョイントインデックスです。(user_id、friend_name、friend_addr)のジョイントインデックスを作成するだけです。このように、このインデックスを介してターゲットデータを取得でき、friend_nameフィールドはすでにソートされています。friend_addrフィールドもあります。テーブルに戻ったり、再度ソートしたりすることなく、1回で完了します。したがって、上記の SQL の場合、一般的なプロセスは次のようになります。

  • ジョイントインデックスを通じてuser_id=10086のデータを見つけ、対応するfriend_nameとfriend_addrフィールドを読み取って直接返します。friend_nameはすでにソートされており、追加の処理は不要です。
  • 最初の手順を繰り返し、10086 以外の最初のデータが見つかるまで、リーフ ノードに沿って逆方向に検索を続けます。

共同インデックスはこの問題を解決できますが、実際のアプリケーションでは盲目的に構築すべきではありません。実際のビジネス ロジックに基づいて構築する必要があるかどうかを判断する必要があります。類似のクエリが頻繁に発生しない場合は、共同インデックスによってストレージ スペースとメンテナンス コストが増加するため、構築する必要はありません。

要約する

  1. order by ステートメントでインデックスが使用されない場合は、explain ステートメントの Extra フィールドに「using filesort」という語句が表示されます。
  2. 「using filesort」が表示されても慌てないでください。データ量が多くない場合、たとえば数十個のデータだけの場合は、ソート バッファーでクイック ソートを使用すると非常に高速になります。
  3. データ量が多く、ソート バッファのサイズを超える場合は、一時ファイル ソート (マージ ソート) が必要になります。これは、MySQL オプティマイザによって決定されます。
  4. クエリに多数のフィールドがあり、ソートに一時ファイルを使用しないようにしたい場合は、max_length_for_sort_data フィールドのサイズを、すべてのクエリ フィールドの長さの合計よりも小さく設定してみてください。これにより、問題を回避できる可能性がありますが、テーブルを返す操作が 1 つ多く発生します。
  5. 実際のビジネスでは、頻繁にクエリされるフィールドの組み合わせに対してジョイントインデックスを作成することもできます。これにより、テーブルに戻ったり、個別に並べ替えたりする必要がなくなりますが、ジョイントインデックスはより多くのストレージとオーバーヘッドを占有します。
  6. 大量のデータをクエリする場合は、バッチでクエリを実行し、事前に説明して SQL 実行プランを確認することをお勧めします。

上記はMySQLデータベースの並べ替えの詳細な内容です。MySQLデータベースの並べ替えの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • ランダムソート結果を使用したデータベースクエリソートの例 (Oracle/MySQL/MS SQL Server)
  • MySQLクエリステートメントは、クエリされる行の数を制限するためにlimitを使用します。
  • MySQL で中国語データをピンインでソートする 2 つの方法
  • MySQL でインデックスを使用してデータをソートする基本的なチュートリアル
  • MYSQL 必読ノート 第 5 章 データのソートと取得
  • Yii2 は MySQL データベース間の関連クエリソート機能コードを実装します
  • MySQL の昇順および降順データソートの実装
  • MySQL の制限クエリとデータソートの概要

<<:  Docker コンテナのタイムゾーン エラーの問題

>>:  1 行のコードでさまざまな IE 互換性の問題を解決します (IE6-IE10)

推薦する

CSS でハニカム/六角形アトラスを実装するためのサンプルコード

理由は分かりませんが、UIではハニカム効果(手を広げたような効果)のデザインが好まれます。 1. 六...

モバイルデバイス上のぼやけた小さなアイコンの問題を解決する方法

序文以前、画像とテキストの垂直方向のずれの問題について説明しました。ここで示した小さな例では、小さな...

MacにMySQLをインストールするときに忘れたパスワードを変更する方法

1. MacにMySQLデータベースをインストールする1. MySQLデータベースをダウンロードする...

MySQL ユーザーと権限、およびルートパスワードをクラックする方法の例

MySQL ユーザーと権限MySQL には、MySQL と呼ばれるシステムに付属するデータベースがあ...

Windows に mysql5.7.28 winx64 の解凍バージョンをインストールするための詳細なチュートリアル

目次1. 解凍する2. データフォルダを作成する3. MySQLに環境変数を追加する3.1 コントロ...

JavaScript 配列のマージのケーススタディ

方法1: var a = [1,2,3]; var b = [4,5] b を連結します。 コンソー...

MySQL 8.0.12 winx64 解凍バージョンのインストール グラフィック チュートリアル

mysql-8.0.12-winx64 解凍版のインストールを記録して、みんなで共有しました。 1....

JavaScript にはすでに Object があるのに、なぜ Map が必要なのでしょうか?

目次1. オブジェクトをマップとして扱わない1. 未定義のプロパティはプロトタイプチェーンを通じてア...

「いいね!」文がインデックスに登録されないのはなぜですか?

序文この記事は、最も人気のある言語で最も退屈な基礎知識を説明することを目的としていますこのトピックは...

Dockerコミットの使い方の詳しい説明

場合によっては、ベースイメージに特定の依存関係をインストールする必要があります。Dockerfile...

HTMLプログラミングタグとドキュメント構造の詳細な説明

HTML を使用してコンテンツをマークアップする目的は、Web ページにセマンティクスを与えることで...

Javascript を使用して、スライドバー効果のあるスライドナビゲーション プラグインを開発します。

目次1. はじめに2. 使用方法3. 開発プロセス1. モデル例2. イベントとアニメーション4. ...

html2canvasで画像が正常にキャプチャできない時の解決方法

質問まず、私が遭遇した問題についてお話しします。まず、そういった需要があるわけです。フロントエンドは...

DockerコンテナのIPアドレスを取得する方法の詳細な説明

1.コンテナに入った後 /etc/hosts を cat するコンテナ自体の IP アドレスと (-...

DOSBox を起動後に自動的にコマンドを実行する方法

DOSBox を使用すると、Windows で DOS をシミュレートし、楽しい作業を行うことができ...