MySQLで高速ランダム選択を行う方法について、いくつかの調査とテストを行ってきました。その過程で私はいくつかの予期しない結果に直面しましたが、今ではORDER BY Rand()が実際にどのように機能するかを完全に知ることができません。
テーブルでORDER BY Rand()を実行すると、MySQLはランダムな値で満たされた新しい列をテーブルに追加し、その列でデータを並べ替えると考えていました。あなたはそこにランダムに到達した上記の値を取ります。私は多くのグーグルとテストを行い、最終的にクエリ Jayは彼のブログで提供しています が実際に最速の解決策であることを発見しました:
SELECT * FROM Table T JOIN (SELECT CEIL(MAX(ID)*Rand()) AS ID FROM Table) AS x ON T.ID >= x.ID LIMIT 1;
一般的なORDER BY Rand()はテストテーブルで30〜40秒かかりますが、彼のクエリは0.1秒で機能します。彼はこれがブログでどのように機能するかを説明しているので、これをスキップして最後に奇妙なことに移ります。
私のテーブルは、PRIMARY KEY id
と、username
、age
などの他のインデックス付けされていないものを含む共通のテーブルです。ここでは、説明に苦労していることがあります。
SELECT * FROM table ORDER BY Rand() LIMIT 1; /*30-40 seconds*/
SELECT id FROM table ORDER BY Rand() LIMIT 1; /*0.25 seconds*/
SELECT id, username FROM table ORDER BY Rand() LIMIT 1; /*90 seconds*/
私は常に1つの列で並べ替えを行っているため、3つのクエリすべてでほぼ同じ時間になると思っていました。しかし、何らかの理由でこれは起こりませんでした。これについて何かアイデアがあれば教えてください。 ORDER BY Rand()を高速に実行する必要があるプロジェクトがあり、個人的に使用したい
SELECT id FROM table ORDER BY Rand() LIMIT 1;
SELECT * FROM table WHERE id=ID_FROM_PREVIOUS_QUERY LIMIT 1;
はい、ジェイの方法よりも遅いですが、それは小さく、理解しやすいです。私のクエリは、いくつかのJOINとWHERE句を含むかなり大きなクエリであり、Jayの方法はまだ機能しますが、JOINされた(クエリではxと呼ばれます)サブリクエストですべてのJOINとWHEREを使用する必要があるため、クエリは非常に大きく複雑になります。
御時間ありがとうございます!
「Rand()による高速注文」などはありませんが、特定のタスクに対する回避策があります。
単一のランダムな行を取得するには、このドイツのブロガーが行うように行うことができます: http://www.roberthartung.de/mysql- order-by-Rand-a-case-study-of-alternatives / (ホットリンクのURLが表示されませんでした。だれかに表示された場合は、リンクを編集してください。)
テキストはドイツ語ですが、SQLコードはページの少し下にあり、大きな白いボックスに入っているので、見づらくはありません。
基本的に彼は、有効な行を取得する処理を行うプロシージャを作成します。これにより、0とmax_idの間の乱数が生成されます。行をフェッチしてみてください。存在しない場合は、ヒットするまで続行してください。彼は一時テーブルにそれらを格納することでx行のランダムな行をフェッチすることを可能にしているので、おそらく1つの行だけをフェッチするよりも少し速くなるようにプロシージャを書き直すことができます。
これの欠点は、ALOTの行を削除し、大きなギャップがある場合、多くの時間を逃す可能性が高くなり、効果がなくなることです。
更新:異なる実行時間
SELECT * FROMテーブルORDER BY Rand()LIMIT 1; /30-40秒/
SELECT id FROM table ORDER BY Rand()LIMIT 1; /0.25秒/
SELECT id、username FROM table ORDER BY Rand()LIMIT 1; /90秒/
私は常に1つの列で並べ替えを行っているため、3つのクエリすべてでほぼ同じ時間になると思っていました。しかし、何らかの理由でこれは起こりませんでした。これについて何かアイデアがあれば教えてください。
索引付けに関係している可能性があります。 id
にはインデックスが付けられ、すばやくアクセスできますが、結果にusername
を追加すると、各行からそれを読み取ってメモリテーブルに配置する必要があります。とともに *
また、すべてをメモリに読み込む必要がありますが、データファイル内を移動する必要がないため、シークに時間を浪費する必要がありません。
これは、各行間の設定された長さ(または0)をスキップするのではなく、可変長の列(varchar/text)がある場合にのみ違いをもたらします。
索引付けに関係している可能性があります。 idにはインデックスが付けられており、すぐにアクセスできますが、結果にユーザー名を追加すると、各行からユーザー名を読み取ってメモリテーブルに配置する必要があります。 *を使用すると、すべてをメモリに読み込む必要もありますが、データファイルをジャンプする必要がないため、シークに時間を浪費する必要がありません。これは、可変長の列がある場合にのみ違いをもたらします。つまり、各行間の設定された長さ(または0)をスキップするのではなく、長さをチェックしてからその長さをスキップする必要があります。
練習はすべての理論よりも優れています!なぜ計画をチェックするだけではないのですか? :)
mysql> explain select name from avatar order by Rand() limit 1;
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
| 1 | SIMPLE | avatar | index | NULL | IDX_AVATAR_NAME | 302 | NULL | 30062 | Using index; Using temporary; Using filesort |
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from avatar order by Rand() limit 1;
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
| 1 | SIMPLE | avatar | ALL | NULL | NULL | NULL | NULL | 30062 | Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
1 row in set (0.00 sec)
mysql> explain select name, experience from avatar order by Rand() limit 1;
+----+-------------+--------+------+--------------+------+---------+------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
| 1 | SIMPLE | avatar | ALL | NULL | NULL | NULL | NULL | 30064 | Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
なぜSELECT id FROM ...
が他の2つよりもはるかに遅いのかはわかりますが、SELECT id, username
がSELECT *
より2〜3倍速い理由はわかりません。
インデックス(この場合は主キー)があり、結果にインデックスの列のみが含まれている場合、MySQLオプティマイザーはインデックスのデータのみを使用でき、テーブル自体も調べません。ファイルシステムIO操作を純粋なメモリ内操作で置き換えるので、各行が高価になるほど、より多くの効果が観察されます。(id、username)に追加のインデックスがある場合、3番目のケースでも同様のパフォーマンスが得られます。
インデックスを追加してみませんかid, username
テーブルで、mysqlがfilesortと一時テーブルだけでなくインデックスを使用するように強制するかどうかを確認します。