MySQLランダムソートの正しい姿勢

テーブル構造があります:
CREATE TABLE `words`(
  ` id` int(11)NOT NULL AUTO_INCREMENT、
  `word` varchar(64)DEFAULT NULL、
  PRIMARY KEY(` id`)
)ENGINE = InnoDB;

テーブルには10,000行が挿入されており、その中から3つの単語がランダムに選択されます。

最も簡単な方法

mysql> rand()制限3による単語の順序から単語を選択します。

このSQLステートメントは非常に単純ですが、実行プロセスは少し複雑です。

「追加」フィールドには、「一時テーブルの使用」と「ファイルソートの使用」が表示され、一時テーブルが必要であり、一時テーブルでソートが必要であることを示します。InnoDBテーブルの場合、フルフィールドソートを実行するとディスクアクセスが減少するため、これが優先されます。

ただし、メモリテーブルの場合、テーブルに戻るプロセスは単にメモリにアクセスしてデータ行の場所に基づいてデータを取得するため、ディスクへの複数のアクセスは発生しません。MySQLはこの時点でROWIDソートを選択します。 。

このステートメントの実行フローは次のようになります。

  1. 一時テーブルを作成します。メモリエンジンが使用されます。テーブルには2つのフィールドがあります。最初のフィールドはdouble型で、フィールドRとしてマークされ、2番目のフィールドはvarchar(64)型で、フィールドWとしてマークされます。また、このテーブルにはインデックスが付けられていません。
  2. 単語テーブルから、主キーの順序ですべての単語値を取り出します。単語の値ごとに、rand()関数を呼び出して、0より大きく1より小さいランダムな小数を生成し、このランダムな小数と単語をそれぞれ一時テーブルのRフィールドとWフィールドに格納します。これまでのスキャン数行は10000です。
  3. これで、一時テーブルには10,000行のデータが含まれます。次に、このインデックス付けされていないメモリ一時テーブルのフィールドRで並べ替える必要があります。
  4. sort_bufferを初期化します。sort_bufferには2つのフィールドがあり、1つはdouble型で、もう1つは整数型です。
  5. R値と位置情報をメモリ一時テーブルから1行ずつフェッチし、sort_bufferの2つのフィールドに格納します。このプロセスでは、全表スキャンが必要です。この時点で、スキャンされる行の数は10,000増加し、20,000になります。
  6. sort_bufferのRの値に従ってソートします。このプロセスにはテーブル操作が含まれないため、スキャンされる行の数は増えないことに注意してください。
  7. ソートが完了すると、最初の3つの結果の位置情報が取得され、単語値が一時メモリテーブルから順番に取得され、クライアントに返されます。このプロセスでは、テーブル内の3行のデータにアクセスし、スキャンされた行の総数は20003になります。

注:ステップ5の「ロケーション情報」の概念は何ですか:MEMORYエンジンは索引構成表ではありません。この例では、それを配列と考えることができます。したがって、このROWIDは実際には配列の添え字です。

遅いログを使用して、次のことを確認します。

# Query_time: 0.900376  Lock_time: 0.000347 Rows_sent: 3 Rows_examined: 20003
SET timestamp=1541402277;
select word from words order by rand() limit 3;

order by rand()はメモリ一時テーブルを使用し、メモリ一時テーブルをソートするときにROWIDソートメソッドが使用されます。

tmp_table_sizeこの構成は、メモリ一時テーブルのサイズを制限します。デフォルト値は16Mです。一時テーブルのサイズがtmp_table_sizeを超えると、メモリ内の一時テーブルがディスク一時テーブルに変換されますディスク一時テーブルに使用されるデフォルトのエンジンはInnoDBであり、パラメーターinternal_tmp_disk_storage_engineによって制御されます

ディスク一時テーブルを使用する場合、上記の例は、明示的なインデックスのないInnoDBテーブルの並べ替えプロセスに対応します。

set tmp_table_size=1024;
set sort_buffer_size=32768;
set max_length_for_sort_data=16;
/* 打开 optimizer_trace,只对本线程有效 */
SET optimizer_trace='enabled=on'; 
/* 执行语句 */
select word from words order by rand() limit 3;
/* 查看 OPTIMIZER_TRACE 输出 */
SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G

    

 sort_modeはROWIDソートを示し、ソートに関係する行はランダム値RフィールドとROWIDフィールドです。

Rフィールドに格納されるランダム値はわずか8バイト、ROWIDは6バイト、データ行の総数は10000です。これは140000バイトであり、sort_buffer_sizeで定義された32768バイトを超えています。ただし、number_of_tmp_filesの値は実際には0です。このSQLステートメントの並べ替えは、MySQL 5.6バージョンで導入された新しい並べ替えアルゴリズム、つまり優先キュー並べ替えアルゴリズムであるためです。OPTIMIZER_TRACEの結果から、filesort_priority_queue_optimizationのchosen = trueの部分も確認できます。

実際、現在のSQLステートメントはR値が最小の3つのROWIDを取得するだけで済みます。マージソートアルゴリズムを使用すると、最初の3つの値を最終的に取得できますが、このアルゴリズムは10,000行すべてをソートします。データ。不要です。

優先キューアルゴリズムは、3つの最小値のみを正確に取得できます。実行プロセスは次のとおりです。

  1. 10,000(R、ROWID)を並べ替えるには、最初に最初の3行を取得して、ヒープを作成します。
  2. 次の行(R '、rowid')を取得し、現在のヒープ内の最大のRと比較します。R 'がRより小さい場合は、この(R、rowid)をヒープから削除し、(R'、rowid)に置き換えます。 ');
  3. 10000番目(R '、rowid')が比較されるまで、手順2を繰り返します。

前回の記事のSQLクエリも1000に制限されています。優先度付きキューアルゴリズムを使用する場合、維持する必要のあるヒープのサイズは1000行(名前、ROWID)であり、設定したsort_buffer_sizeのサイズを超えています。マージソートアルゴリズムのみを使用できます。

つまり、どのタイプの一時テーブルを使用する場合でも、rand()による順序付けは計算プロセスを非常に複雑にし、多数のスキャン行を必要とするため、ソートプロセスのリソース消費は非常に大きくなります。

ランダムに正しく並べ替える

1つの単語値のみがランダムに選択される場合は、最初に問題を単純化します。

  1. このテーブルの主キーIDの最大値Mと最小値Nを取得します。
  2. ランダム関数を使用して、最大値と最小値の間の数値を生成しますX =(MN)* rand()+ N;
  3. X以上の最初のIDを持つ行を取得します。

とりあえずランダムアルゴリズム1と呼ばれているので、実行ステートメントのシーケンスを見てください。

mysql> select max(id),min(id) into @M,@N from t ;
set @X= floor((@M-@N+1)*rand() + @N);
select * from t where id >= @X limit 1;

max(id)とmin(id)の両方がインデックスをスキャンする必要がないため、この方法は非常に効率的です。selectの3番目のステップでも、インデックスを使用してすばやく検索できます。これは、3行のみをスキャンすると見なすことができます。しかし実際には、IDに穴がある可能性があるため、このアルゴリズム自体はタイトルのランダムな要件を厳密には満たしていません。したがって、異なる行を選択する確率は異なり、真にランダムではありません。

厳密にランダムな結果を得るには、次のプロセスを使用できます。

  1. テーブル全体の行数を取得し、Cとして記録します。
  2. Y = floor(C * rand())を取得します。ここでの床関数の役割は、整数部分を取ることです。
  3. 行を取得するには、制限Y、1を使用します。

これはランダムアルゴリズム2であり、アルゴリズム1の明らかな不均一な確率の問題を解決します。制限Y、1を処理するMySQLのアプローチは、それらを1つずつ順番に読み取り、最初のYを破棄してから、次のレコードを戻り結果として使用することです。したがって、このステップではY +1行をスキャンする必要があります。さらに、最初のステップでスキャンされるCラインは、合計でC + Y + 1ラインをスキャンする必要があり、実行コストはランダムアルゴリズム1のコストよりも高くなります。

この表に従って10000行、C = 10000で計算した場合、より大きなY値にランダムである場合、スキャンされた行の数はほぼ20000であり、rand()による順序のスキャンされた行の数に近いですがrand()による注文よりも、実行コストがはるかに低くなります。ランダムアルゴリズム2は、主キーの並べ替えと主キーの自然インデックスの並べ替えに従ってデータを取得するために制限を実行するため、このプロセスはここでは省略されています。

ランダムアルゴリズム2のアイデアに従う場合、3つの単語値をランダムに選択する必要があります:

  1. Cで示されるテーブル全体の行数を取得します。
  2. 同じランダムな方法に従ってY1、Y2、Y3を取得します。
  3. 3つのlimitY、1ステートメントを実行して、3行のデータを取得します。

このランダムアルゴリズムのスキャンラインの総数はC +(Y1 + 1)+(Y2 + 1)+(Y3 + 1)です。実際、スキャンラインの数をさらに減らすために最適化を続けることができます。

  1. Y1、Y2、Y3をランダムに取り出した後、YmaxとYminを計算します。
  2. t制限Yminからidを選択します(Ymax-Ymin + 1);
  3. IDセットを取得したら、Y1、Y2、およびY3に対応する3つのIDを計算します。
  4. 最后select * from t where id in(id1、id2、id3)。

この方法でスキャンされる行数は、C + Ymax +3である必要があります。

 

 

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

おすすめ

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