クエリとパフォーマンスの最適化に参加する

実際の本番環境では、結合ステートメントの使用に関する質問は、通常、次の2つのカテゴリに焦点を当てています。

  • DBAは結合の使用を許可していませんが、結合の使用に関する問題は何ですか?
  • 結合するサイズが異なる2つのテーブルがある場合、どちらのテーブルを駆動テーブルとして使用する必要がありますか?

例を挙げて何かを言います。

CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB;

create table t1 like t2;

1000行のデータがテーブルt2に挿入され、100行のデータがテーブルt1に挿入されます。

インデックスネストループ結合

そのようなクエリがあります:select * from t1 Straight_join t2 on(t1.a = t2.a);

結合ステートメントを直接使用する場合、MySQLオプティマイザはテーブルt1またはt2を駆動テーブルとして選択する可能性があります。これは、SQLステートメントの分析の実行プロセスに影響を与えます。したがって、実行中のパフォーマンスの問題の分析を容易にするために、straight_joinに切り替えて、MySQLが固定接続メソッドを使用してクエリを実行できるようにし、オプティマイザーが指定された方法でのみ結合するようにしますこのステートメントでは、t1はドリブンテーブルであり、t2はドリブンテーブルです。

ご覧のとおり、このステートメントでは、ドリブンテーブルt2のフィールドaにインデックスがあり、このインデックスは結合プロセスで使用されるため、このステートメントの実行フローは次のようになります。

  1. テーブルt1からデータRの行を読み取り、データ行Rからフィールドを取得してテーブルt2を検索します。
  2. 表t2の条件を満たす行を取り出し、結果セットの一部としてRを使用して行を形成します。
  3. テーブルt1のループの終わりが終了するまで、手順1と2を繰り返します。

形式的には、このプロセスはプログラムを作成するときのネストされたクエリに似ており、ドリブンテーブルのインデックスを使用できるため、「インデックスネストループ結合」または略してNLJと呼びます。実行プロセス全体で、スキャンラインの総数は200です。

結合が使用されていないと仮定すると、単一のテーブルクエリのみを使用できます。

1. select * from t1を実行して、100行あるテーブルt1のすべてのデータを見つけます。

2.次の100行のデータをループします。

  • 各行Rからフィールドa $ Raの値を取得します。
  • 执行select * from t2 where a = $ Ra;
  • 返された結果とRは、結果セットの行を形成します。

このクエリプロセスでは、200行がスキャンされましたが、合計101のselectステートメントが実行されました。これは、直接結合よりも100多いインタラクションです。さらに、クライアントはSQLステートメントと結果を自分でスプライスする必要があります。もちろん、joinを使用することをお勧めします。

この結合ステートメントの実行中、駆動テーブルは全表スキャンであり、駆動テーブルはツリー検索です。

ドリブンテーブルの行数をMとすると、ドリブンテーブルのデータの行をチェックするたびに、最初にインデックスaを検索してから、主キーインデックスを検索する必要があります。毎回ツリーを検索するおおよその複雑さは、2を底とするMの対数であり、log2Mとして記録されるため、被駆動テーブルの行を検索する時間計算量は2 * log2M(通常のインデックス検索+主キーインデックス検索)です。 。

駆動テーブルの行数がNであるとすると、実行プロセスは駆動テーブルをN行スキャンする必要があり、各行について、駆動テーブルで1回一致します。したがって、実行プロセス全体のおおよその複雑さはN + N * 2 * log2Mです。明らかに、Nはスキャン行の数に大きな影響を与えるため、小さなテーブルを駆動テーブルとして使用する必要があります

上記の分析を通じて、2つの結論が得られました。

  • 結合ステートメントを使用すると、SQLステートメントを実行するために複数の単一テーブルに強制的に分割するパフォーマンスよりもパフォーマンスが向上します。
  • 結合ステートメントを使用する場合は、小さなテーブルを駆動テーブルにする必要があります。

ただし、この結論の前提は、「ドリブンテーブルのインデックスを使用できる」ということであることに注意する必要があります

単純なネストされたループ結合

SQLステートメントを次のように変更します。select* from t1 Straight_join t2 on(t1.a = t2.b);

テーブルt2のフィールドbにはインデックスがないため、t2が一致するたびに全表スキャンが必要です。結果だけを見ると、このアルゴリズムは正しく、このアルゴリズムには「SimpleNested-LoopJoin」という名前もあります。

このようにして、このSQL要求はテーブルt2を最大1,000回スキャンし、合計100 * 1000 = 100,000行をスキャンします。これらが2つの大きなテーブルである場合、このアルゴリズムは「面倒」に見えます。

もちろん、MySQLはこのSimple Nested-Loop Joinアルゴリズムを使用しませんが、BNLと呼ばれる「BlockNested-LoopJoin」と呼ばれる別のアルゴリズムを使用します。

ネストされたループ結合をブロックする

現時点では、ドリブンテーブルに使用可能なインデックスはなく、アルゴリズムフローは次のようになります。

  • テーブルt1のデータをスレッドメモリjoin_bufferに読み込みます。このステートメントにselect *を書き込むので、テーブルt1全体をメモリに入れます。
  • テーブルt2をスキャンし、テーブルt2の各行を取り出し、join_bufferのデータと比較して、結果セットの一部として結合条件を満たすものを返します。

このプロセスでは、テーブルt1とt2の両方で全表スキャンが実行されるため、スキャンされる行の総数は1100であることがわかります。join_bufferは順序付けられていない配列で編成されているため、テーブルt2の各行に100の判定が必要です。メモリに必要な判定の総数は、100 * 1000 = 100,000回です。

以前の単純なネストループ結合アルゴリズムと比較して、このアルゴリズムの100,000の判断はメモリ操作であり、はるかに高速でパフォーマンスが優れています。

この場合、どのテーブルを駆動テーブルとして選択する必要があるかを見てみましょう。小さいテーブルの行数がNで、大きいテーブルの行数がMであるとすると、このアルゴリズムでは次のようになります。

  • 両方のテーブルが全表スキャンを実行するため、スキャンされる行の総数はM + Nです。
  • メモリ内の判定数はM * Nです。

この2つの計算でMとNに差がないことがわかります。したがって、この時点では、駆動テーブルとして大きなテーブルを選択するか小さなテーブルを選択するかにかかわらず、実行時間は同じです。

join_bufferのサイズはパラメーターjoin_buffer_sizeによって設定され、デフォルト値は256kです。テーブルt1にすべてのデータを配置できない場合、戦略は非常に単純で、セクションに配置することです。join_buffer_sizeを1200に変更して実行しました。実行プロセスは次のようになります。

  1. テーブルt1をスキャンし、データ行を順番に読み取り、join_bufferに配置します。88番目の行にjoin_bufferを配置した後、手順2に進みます。
  2. テーブルt2をスキャンし、t2の各行を取り出し、それをjoin_bufferのデータと比較して、結果セットの一部として結合条件を満たすものを返します。
  3. join_bufferをクリアします。
  4. テーブルt1のスキャンを続行し、最後の12行のデータをjoin_bufferに順次読み取り、ステップ2に進みます。

このプロセスは、「ブロックで結合」を意味するアルゴリズム名の「ブロック」の起源を反映しています。このとき、テーブルt1が2回に分割され、join_bufferに入れられるため、テーブルt2が2回スキャンされることがわかります。join_bufferは2回に分割されますが、等価条件を決定する回数は変わりません。それでも(88 + 12)* 1000 = 100,000回です。

この場合のドライバーテーブルの選択を見てみましょう。被駆動テーブルのデータ行数をNとすると、アルゴリズムフローを完了するには、Kセグメントに分割する必要があり、被駆動テーブルのデータ行数はMです。ここでのKは定数ではないことに注意してください。Nが大きいほど、Kも大きくなります。したがって、Kはλ* Nとして表され、λの値の範囲は(0,1)です。したがって、このアルゴリズムの実行では、スキャンラインの数はN +λ* N * Mであり、メモリはN * M回判断されます。

明らかに、メモリ判定の数は、どのテーブルが駆動テーブルとして選択されているかによって影響を受けません。走査線の数を考慮すると、MとNのサイズを決定すると、Nが小さくなり、計算全体の結果が小さくなります。小さなテーブルを運転テーブルとして使用する必要があります

Nが大きいほど、セグメントKの数が多くなると言いました。次に、Nが固定されている場合、join_buffer_sizeが大きいほど、セグメントの数が少なくなり、被駆動テーブルの全表スキャンが少なくなります。

そのため、joinステートメントが非常に遅い場合は、join_buffer_sizeを増やすという提案が表示される場合があります

最初の2つの質問に答える

結合ステートメントを使用できますか?

  • Index Nested-Loop Joinアルゴリズムを使用できる場合、つまり、ドリブンテーブルのインデックスを使用できる場合は、実際には問題ありません。
  • Block Nested-Loop Joinアルゴリズムを使用する場合、スキャンラインの数が多すぎます。特に大きなテーブルでの結合操作の場合、これはドリブンテーブルを何度もスキャンする必要があり、多くのシステムCPUリソースを消費します。したがって、この種の結合は使用しないようにしてください。

したがって、joinステートメントを使用するかどうかを決定するときは、explainの結果を見て、「BlockNestedLoop」という単語がExtraフィールドに表示されるかどうかを確認します

結合を使用する場合、駆動テーブルとして大きなテーブルを選択するか、駆動テーブルとして小さなテーブルを選択する必要がありますか?

  • Index Nested-Loop Joinアルゴリズムの場合は、駆動テーブルとして小さなテーブルを選択する必要があります。
  • それがBlockNested-Loop Joinアルゴリズムの場合:join_buffer_sizeが十分に大きい場合、それは同じです。join_buffer_sizeが十分に大きくない場合(この状況がより一般的です)、小さなテーブルを駆動テーブルとして選択する必要があります。

したがって、この質問の結論は、小さなテーブルを常に駆動テーブルとして使用する必要があるということです。

「小さな時計」が誰であるかを判断する方法は

1. select * from t1 Straight_join t2 on(t1.b = t2.b)where t2.id <= 50;
    select * from t2 Straight_join t1 on(t1.b = t2.b)where t2.id <= 50;

2つのステートメントのドリブンテーブルがインデックスを使用しないようにするために、結合フィールドはインデックスのないフィールドbを使用することに注意してください。ただし、2番目のステートメントを使用する場合は、join_bufferをt2の最初の50行に配置するだけで済み、明らかに優れています。したがって、ここでは、「t2の最初の50行」は比較的小さなテーブル、つまり「小さなテーブル」です。

2. t1 Straight_join t2 on(t1.b = t2.b)からt1.b、t2。*を選択します。ここでt2.id <= 100;
    t2からt1.b、t2。*を選択します。straight_joint1on(t1.b = t2.b)ここで、t2.id <= 100;

この例では、テーブルt1とt2の両方で、結合に参加している行は100行のみです。ただし、これら2つのステートメントでjoin_bufferに入れられるデータは、毎回異なります。

  • テーブルt1はフィールドbのみをチェックするため、t1をjoin_bufferに入れる場合は、bの値だけをjoin_bufferに入れる必要があります。
  • テーブルt2はすべてのフィールドをチェックする必要があるため、テーブルt2をjoin_bufferに配置する場合は、3つのフィールドid、a、およびbを配置する必要があります。

ここでは、駆動テーブルとしてテーブルt1を選択する必要があります。「結合に参加しているテーブルt1の1つの列のみ」は、比較的小さいテーブルです。

したがって、より正確には、どちらのテーブルを駆動テーブルにするかを決定する際に、2つのテーブルをそれぞれの条件に従ってフィルタリングする必要があります。フィルタリングが完了した後、結合に参加している各フィールドの合計データ量が計算され、データ量の少ないテーブルは「小さなテーブル」であり、駆動テーブルとして使用する必要があります。

MRR最適化とBKA最適化

1.マルチレンジ読み取り最適化(MRR)。この最適化の主な目的は、可能な限り順次ディスク読み取りを使用することです。

ほとんどのデータは主キーの昇順で挿入されるため、クエリが主キーの昇順である場合、ディスク読み取りは順次読み取りに近くなり、読み取りパフォーマンスが向上すると考えられます。

select * from t1 where a> = 1 and a <= 100; aは通常のインデックスです。ステートメントの実行フローは次のようになります。

  1. インデックスaに従って、条件を満たすレコードを見つけ、id値をread_rnd_bufferに入れます。
  2. read_rnd_bufferのIDを昇順で並べ替えます。
  3. id配列を並べ替えた後、主キーIDインデックスのレコードを順番に確認し、結果として返します。

ここで、read_rnd_bufferのサイズは、read_rnd_buffer_sizeパラメーターによって制御されます。手順1でread_rnd_bufferがいっぱいになると、手順2と3が最初に実行され、次にread_rnd_bufferがクリアされます。次に、インデックスaの次のレコードの検索を続行し、ループを続行します。

もう1つ注意すべき点は、MRR最適化を着実に使用する場合は、setoptimizer_switch = "mrr_cost_based = off"を設定する必要があることです(公式ドキュメントには、これが現在のオプティマイザー戦略であると記載されています。消費量を判断するときは、より傾向があります。 MRRを使用しない)。

説明の結果から、ExtraフィールドにMRRの使用が多いことがわかります。これは、MRR最適化が使用されていることを意味します。

概要:MRRがパフォーマンスを向上させることができるコアは、このクエリステートメントがインデックスaの範囲クエリまたは複数値クエリであり、十分な主キーIDを取得できるため、並べ替えた後、主キーインデックスに移動してデータ。「シーケンス」の利点を反映しています。

2. MySQLは、バージョン5.6以降、Batched Key Access(BKA)アルゴリズムの導入を開始しました。このBKAアルゴリズムは、実際にはNLJアルゴリズムの最適化です。

なぜなら、NLJアルゴリズムによって実行されるロジックは次のとおりです:ドリブンテーブルから行ごとに値をフェッチしてから、ドリブンテーブルを結合します。つまり、ドリブンテーブルの場合、値は毎回一致します。現時点では、MRRの利点は使用されません。さらに、BNLアルゴリズムでのjoin_bufferの役割は、ドライブテーブルのデータを一時的に保存することであることがわかっています。ただし、NLJアルゴリズムでは役に立ちません。次に、join_bufferをBKAアルゴリズムに再利用できます。

BKA最適化アルゴリズムを使用する場合は、SQLステートメントを実行する前に設定する必要があります。

set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

その中で、最初の2つのパラメーターの機能は、MRRを有効にすることです。これは、BKAアルゴリズムの最適化がMRRに依存するためです。

パフォーマンスの問題とBNLアルゴリズムの最適化

Block Nested-Loop Join(BNL)アルゴリズムを使用する場合、ドリブンテーブルが複数回スキャンされることがあります。被駆動テーブルが大きなコールドデータテーブルである場合、高いIO圧力を引き起こすことに加えて、システムにどのような影響がありますか?

前述のように、InnoDBはBuffferPoolのLRUアルゴリズムを最適化しました。ただし、BNLアルゴリズムを使用する結合ステートメントがコールドテーブルを複数回スキャンし、ステートメントの実行時間が1秒を超える場合、コールドテーブルのデータページは、コールドテーブルがLRUリンクリストの先頭に移動されます。再度スキャンされます。この状況は、コールドテーブルのデータ量がバッファプール全体の3/8未満であり、完全に古い領域に配置できる状況に対応します。

コールドテーブルが非常に大きい場合、別の状況が発生します。通常、企業がアクセスするデータページは、若い領域に入る機会がありません。通常アクセスされるデータページは、若いエリアに入るには1秒後に再度アクセスする必要があるためです。ただし、joinステートメントはループでディスクを読み取り、メモリページを削除するため、古い領域に入るデータページは1秒以内に削除される可能性があります。このように、このMySQLインスタンスのバッファプールの若い領域のデータページは、この期間中に合理的に削除されません。

大規模なテーブル結合操作はIOに影響を与えますが、IOへの影響はステートメントの実行後に終了します。ただし、バッファプールへの影響は継続的であり、メモリヒット率をゆっくりと復元するには、後続のクエリ要求が必要です。

要約すると、システムに対するBNLアルゴリズムの影響には、主に3つの側面が含まれます。

  • ドリブンテーブルは複数回スキャンされ、ディスクIOリソースを占有する可能性があります。
  • 結合条件を決定するには、M * N比較を実行する必要があります(MとNは、それぞれ2つのテーブルの行数です)。大きなテーブルの場合、多くのCPUリソースを消費します。
  • バッファプールのホットデータが削除され、メモリヒット率に影響を与える可能性があります。

ステートメントを実行する前に、理論的分析を通じてBNLアルゴリズムを使用するかどうかを確認し、説明の結果を表示する必要があります。オプティマイザがBNLアルゴリズムを使用することが確認された場合は、最適化が必要です。一般的な最適化方法は、被駆動テーブルの結合フィールドにインデックスを追加して、BNLアルゴリズムをBKAアルゴリズムに変換することです。

このステートメントが同時に低頻度のSQLステートメントである場合、このステートメントのテーブルt2のフィールドbにインデックスを作成するのは無駄です。ただし、前述のようにBNLアルゴリズムを使用して結合する場合は、メモリ内でBNLアルゴリズムを判断する必要があり、テーブルが大きくなると急激に増加し、時間がかかりすぎます。

現時点では、一時テーブルの使用を検討できます。一時テーブルを使用する一般的な考え方は次のとおりです:

  • まず、条件に一致するデータを一時テーブルの大きなテーブルに配置します。
  • 結合でBKAアルゴリズムを使用できるようにするには、一時テーブルのフィールドにインデックスを追加します。
  • 駆動テーブルと一時テーブルに結合操作を実行させます。

これにより、大きなテーブルをスキャンする際のメモリ判定の数が減ります。

一般に、元のテーブルにインデックスを追加する場合でも、インデックス付きの一時テーブルを使用する場合でも、結合ステートメントでドリブンテーブルのインデックスを使用して、BKAアルゴリズムをトリガーし、クエリのパフォーマンスを向上させることができます。

拡張1-ハッシュ結合

join_bufferに保持されているのが順序付けられていない配列ではなくハッシュテーブルである場合、多数の判定が少数のハッシュルックアップになり、ステートメント全体の実行速度がはるかに速くなります。MySQLの現在のバージョンはハッシュ結合をサポートしていないため、アプリケーション側でシミュレートできます。理論的には、効果は一時テーブルソリューションよりも優れています。

実装プロセスはおおまかに次のとおりです。

  • select * from t1;小さなテーブルt1のすべての行データを取得し、C ++で設定されたデータ構造やPHPで配列されたデータ構造など、ビジネス側のハッシュ構造を格納します。
  • select * from t2 where ...テーブルt2で条件を満たす複数行のデータを取得します。これらの複数行のデータを1行ずつビジネスの終わりまで取得し、ハッシュ構造データテーブルで一致するデータを検索します。一致条件を満たすデータの行は、結果セットの行と見なされます。

拡張2-マルチテーブル結合

如:select * from t1  join t2 on (t1.a = t2.a)  join t3 on  (t2.b = t3.b)  where   t1.c> = X and t2.c> = Y and t3.c> = Z;

Straight_joinと書き直した場合、接続順序の指定方法と3つのテーブルのインデックスの作成方法。

最初の原則は、可能な限りBKAアルゴリズムを使用することです。BKAアルゴリズムを使用する場合、「最初に2つのテーブルの結合の結果を計算し、次に3番目のテーブルを結合する」のではなく、クエリを直接ネストすることに注意してください。

具体的な実装は次のとおりです。3つの条件t1.c> = X、t2.c> = Y、t3.c> = Zの中から、フィルタリング後のデータが最も少ないテーブルを最初の駆動テーブルとして選択しますこのとき、次の2つの状況が発生する可能性があります。

前者の場合、テーブルt1またはt3を選択すると、残りの部分が固定されます。

  • 駆動テーブルがt1の場合、接続シーケンスはt1-> t2-> t3であり、インデックスは被駆動テーブルのフィールド、つまりt2.aとt3.bに作成する必要があります。
  • 駆動テーブルがt3の場合、接続シーケンスはt3-> t2-> t1であり、インデックスはt2.bとt1.aに作成する必要があります。

同時に、最初の駆動テーブルのフィールドcにインデックスを作成する必要もあります。

2番目のケースは、最初に選択された駆動テーブルがテーブルt2である場合、他の2つの条件のフィルタリング効果を評価する必要がある場合です。

つまり、全体的な考え方は、結合に参加している駆動テーブルのデータセットをできるだけ小さくすることです。これにより、駆動テーブルが小さくなるためです。

 

コンテンツソース:LinXiaobin「MySQLの実際の戦闘に関する45の講義」

 

 

おすすめ

転載: blog.csdn.net/qq_24436765/article/details/112857658