大きなmysqlテーブルからランダムな行を選択する高速な方法は何ですか?
私はphpで作業していますが、別の言語であっても解決策に興味があります。
すべてのIDを取得し、そこからランダムなIDを選択して、行全体を取得します。
IDが穴のない連続していることがわかっている場合は、最大値を取得してランダムIDを計算できます。
あちこちに穴がありますが、ほとんどが連続した値であり、わずかに歪んだランダム性を気にしない場合は、最大値を取得してIDを計算し、計算したID以上のIDを持つ最初の行を選択します。スキューの理由は、そのような穴をフォローしているIDは、別のIDをフォローしているIDよりも選択される可能性が高いためです。
ランダムに注文すると、ひどいテーブルスキャンが手元にあり、クイックという言葉はそのようなソリューションには当てはまりません。
そうしないでください。GUIDで注文する必要もありません。同じ問題が発生します。
1 回のクエリですばやく実行する方法が必要であることはわかっていました。そして、ここにあります:
外部コードを使用しない高速な方法です。
http://jan.kneschke.de/projects/mysql/order-by-rand/
SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1;
MediaWikiは興味深いトリックを使用しています(ウィキペディアのSpecial:Random機能の場合):記事を含むテーブルには、乱数(記事の作成時に生成される)を含む追加の列があります。ランダムな記事を取得するには、乱数を生成し、[乱数]列の値が次に大きいまたは小さい(どちらかを思い出さないでください)記事を取得します。インデックスを使用すると、これは非常に高速になります。(そしてMediaWikiはPHPで書かれており、MySQL用に開発されています。)
結果の数値が適切に分散されていない場合、このアプローチは問題を引き起こす可能性があります。IIRC、これはMediaWikiで修正されているので、この方法で行うことにした場合は、コードを調べて現在どのように行われているかを確認する必要があります(おそらく、乱数列を定期的に再生成します)。
これは、かなり高速に実行されるソリューションであり、id 値が連続しているか、1 から始まるかに依存することなく、より適切なランダム分布を取得します。
SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*) FROM mytable)));
SET @sql := CONCAT('SELECT * FROM mytable LIMIT ', @r, ', 1');
PREPARE stmt1 FROM @sql;
EXECUTE stmt1;
たぶん、次のようなことができます:
SELECT * FROM table
WHERE id=
(FLOOR(RAND() *
(SELECT COUNT(*) FROM table)
)
);
これは、ID 番号がすべて連続しており、ギャップがないことを前提としています。
計算されたランダム値を含む列を各行に追加し、それを順序句で使用して、選択時に 1 つの結果に制限します。これは、原因となるテーブル スキャンを実行するよりも高速に機能しますORDER BY RANDOM()
。
更新:SELECT
もちろん、検索時にステートメントを発行する前に、ランダムな値を計算する必要があります。
SELECT * FROM `foo` WHERE `foo_rand` >= {some random value} LIMIT 1
テーブルからランダムな行を見つけるために、ORDER BY RAND() を使用しないでください。これは、MySQL が完全なファイル ソートを実行し、その後で必要な制限行数を取得することを強制するためです。この完全なファイルの並べ替えを回避するには、RAND() 関数を where 句でのみ使用します。必要な行数に達するとすぐに停止します。http://www.rndblog.com/how-to-select-random-rows-in-mysql/を参照して ください。
このテーブルの行を削除しない場合、最も効率的な方法は次のとおりです。
(最小IDがわかっている場合はスキップしてください)
SELECT MIN(id) AS minId, MAX(id) AS maxId FROM table WHERE 1
$randId=mt_rand((int)$row['minId'], (int)$row['maxId']);
SELECT id,name,... FROM table WHERE id=$randId LIMIT 1
注文すると、yoは全表スキャンテーブルを実行します。select count(*)を実行し、後で0と最後のレジストリの間でランダムなrow=rownumを取得する場合に最適です
擬似コードの場合:
sql "select id from table"
store result in list
n = random(size of list)
sql "select * from table where id=" + list[n]
id
これは、が一意の(主)キーであることを前提としています。
私の場合、テーブルには主キーとして ID があり、ギャップなしで自動インクリメントされるため、COUNT(*)
またはMAX(id)
を使用して行数を取得できます。
最速の操作をテストするために、次のスクリプトを作成しました。
logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();
結果は次のとおりです。
36.8418693542479 ms
0.241041183472 ms
0.216960906982 ms
order メソッドで答えます。
SELECT FLOOR(RAND() * (
SELECT id FROM tbl ORDER BY id DESC LIMIT 1
)) n FROM tbl LIMIT 1
...
SELECT * FROM tbl WHERE id = $result;
私はSQLに少し慣れていませんが、PHPで乱数を生成して使用するのはどうですか
SELECT * FROM the_table WHERE primary_key >= $randNr
これは、テーブルの穴の問題を解決しません。
しかし、これは lassevks の提案にひねりを加えたものです。
SELECT primary_key FROM the_table
PHP で mysql_num_rows() を使用して、上記の結果に基づいて乱数を作成します。
SELECT * FROM the_table WHERE primary_key = rand_number
に基づいて乱数を作成し、データ ポインタをそのポイントに移動するのがいかに遅いかについては、補足として説明しSELECT * FROM the_table
ます。これは、たとえば 100 万行の大きなテーブルではどれくらい遅くなるでしょうか?mysql_num_rows()
mysql_data_seek()
ID が連続していないという問題に遭遇しました。私が思いついたのはこれです。
SELECT * FROM products WHERE RAND()<=(5/(SELECT COUNT(*) FROM products)) LIMIT 1
返される行は約 5 行ですが、1 行に制限しています。
別の WHERE 句を追加する場合は、もう少し興味深いものになります。割引商品を検索したいとします。
SELECT * FROM products WHERE RAND()<=(100/(SELECT COUNT(*) FROM pt_products)) AND discount<.2 LIMIT 1
あなたがしなければならないことは、十分な結果を返すことを確認することです。そのため、100 に設定しています。サブクエリに WHERE discount<.2 句があると 10 倍遅くなるため、より多くの結果を返して制限することをお勧めします。
Jan Kneschke によるこのリンクまたはこの SO 回答を見てください。どちらも同じ質問について話し合っているためです。SOの回答にはさまざまなオプションがあり、ニーズに応じていくつかの良い提案があります. Jan は、さまざまなオプションとそれぞれのパフォーマンス特性について詳しく説明します。彼は、MySQL select 内でこれを行うための最も最適化された方法を次のようにまとめました。
SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1;
HTH、
-ディピン
古典的な「SELECT id FROM table ORDER BY RAND() LIMIT 1」は実際には問題ありません。
MySQL マニュアルからの次の抜粋を参照してください。
ORDER BY で LIMIT row_count を使用する場合、MySQL は、結果全体をソートするのではなく、ソートされた結果の最初の row_count 行が見つかるとすぐにソートを終了します。
簡単だが遅い方法は(小さなテーブルに適しています)
SELECT * from TABLE order by RAND() LIMIT 1
SET @COUNTER=SELECT COUNT(*) FROM your_table;
SELECT PrimaryKey
FROM your_table
LIMIT 1 OFFSET (RAND() * @COUNTER);
MyISAM テーブルの場合、最初のクエリの複雑さは O(1) です。
2 番目のクエリには、テーブルのフル スキャンが伴います。複雑さ = O(n)
この目的のためだけに別のテーブルを保持してください。元のテーブルに挿入するときは常に、このテーブルにも同じ行を挿入する必要があります。前提: DELETE はありません。
CREATE TABLE Aux(
MyPK INT AUTO_INCREMENT,
PrimaryKey INT
);
SET @MaxPK = (SELECT MAX(MyPK) FROM Aux);
SET @RandPK = CAST(RANDOM() * @MaxPK, INT)
SET @PrimaryKey = (SELECT PrimaryKey FROM Aux WHERE MyPK = @RandPK);
DELETE が許可されている場合、
SET @delta = CAST(@RandPK/10, INT);
SET @PrimaryKey = (SELECT PrimaryKey
FROM Aux
WHERE MyPK BETWEEN @RandPK - @delta AND @RandPK + @delta
LIMIT 1);
全体的な複雑さは O(1) です。