MySQLのORDER BY Rand()
関数のいくつかの代替案について読みましたが、ほとんどの代替案は、単一のランダムな結果が必要な場所にのみ適用されます。
次のような複数のランダムな結果を返すクエリを最適化する方法はありますか?
SELECT u.id,
p.photo
FROM users u, profiles p
WHERE p.memberid = u.id
AND p.photo != ''
AND (u.ownership=1 OR u.stamp=1)
ORDER BY Rand()
LIMIT 18
このソリューションは、インデックス列を使用すると最適に機能します。
次に、100,000行でマークされたクエリベンチの簡単な例と最適化を示します。
最適化:0ms
_SELECT
g.*
FROM
table g
JOIN
(SELECT
id
FROM
table
WHERE
Rand() < (SELECT
((4 / COUNT(*)) * 10)
FROM
table)
ORDER BY Rand()
LIMIT 4) AS z ON z.id= g.id
_
制限量に関する注意:制限4および4/count(*)。 4は同じ数である必要があります。返す数を変更しても、速度にはそれほど影響しません。制限4と制限1000のベンチマークは同じです。制限10,000で最大600ミリ秒かかりました
結合についての注意:行全体をランダム化するよりも、idのみをランダム化する方が高速です。行全体をメモリにコピーしてからランダム化する必要があるためです。結合は、テーブルクエリを防ぐために、サブクエリにリンクされている任意のテーブルにすることができます。
where句に注意:whereカウントは、ランダム化される結果の量を制限します。結果全体の割合を取り、テーブル全体ではなくソートします。
サブクエリに注意:ジョインを行う場合と追加のwhere句条件をサブクエリとサブサブクエリの両方に配置する必要があります。正確なカウントを取得し、正しいデータを取得します。
最適化されていない:1200ms
_SELECT
g.*
FROM
table g
ORDER BY Rand()
LIMIT 4
_
[〜#〜] pros [〜#〜]
order by Rand()
より4倍高速です。このソリューションは、インデックス付き列を持つ任意のテーブルで機能します。
[〜#〜] cons [〜#〜]
複雑なクエリでは少し複雑です。サブクエリで2つのコードベースを維持する必要がある
これは代替手段ですが、Rand()の使用に基づいています。
SELECT u.id,
p.photo,
ROUND(Rand() * x.m_id) 'Rand_ind'
FROM users u,
profiles p,
(SELECT MAX(t.id) 'm_id'
FROM USERS t) x
WHERE p.memberid = u.id
AND p.photo != ''
AND (u.ownership=1 OR u.stamp=1)
ORDER BY Rand_ind
LIMIT 18
これは少し複雑ですが、random_ind値のより良い分布を与えました:
SELECT u.id,
p.photo,
FLOOR(1 + Rand() * x.m_id) 'Rand_ind'
FROM users u,
profiles p,
(SELECT MAX(t.id) - 1 'm_id'
FROM USERS t) x
WHERE p.memberid = u.id
AND p.photo != ''
AND (u.ownership=1 OR u.stamp=1)
ORDER BY Rand_ind
LIMIT 18
最速ではありませんが、一般的なORDER BY Rand()
方法よりも高速です:
ORDER BY Rand()
は、インデックス付き列のみを検索するために使用する場合、それほど遅くありません。次のように、1つのクエリですべてのIDを取得できます。
_SELECT id
FROM testTable
ORDER BY Rand();
_
ランダムIDのシーケンスを取得し、他のSELECTまたはWHEREパラメーターを使用した別のクエリへの結果をJOIN
:
_SELECT t.*
FROM testTable t
JOIN
(SELECT id
FROM `testTable`
ORDER BY Rand()) AS z ON z.id= t.id
WHERE t.isVisible = 1
LIMIT 100;
_
あなたの場合、それは次のようになります:
_SELECT u.id, p.photo
FROM users u, profiles p
JOIN
(SELECT id
FROM users
ORDER BY Rand()) AS z ON z.id = u.id
WHERE p.memberid = u.id
AND p.photo != ''
AND (u.ownership=1 OR u.stamp=1)
LIMIT 18
_
これは非常に鈍い方法であり、非常に大きなテーブルでは適切ではありませんが、それでも一般的なRand()
よりも高速です。ほぼ400000で3000のランダムな行を検索する実行時間は20倍速くなりました。
カラムを作成するか、乱数(たとえばphpで生成)を使用してselectに結合し、このカラムで並べ替えます。
今日、これに遭遇し、「DISTINCT」をJOINと一緒に使用しようとしましたが、ランドが各JOINされた行を区別しているため、重複していると思います。私は少し混乱して、次のように機能する解決策を見つけました:
SELECT DISTINCT t.id,
t.photo
FROM (SELECT u.id,
p.photo,
Rand() as Rand
FROM users u, profiles p
WHERE p.memberid = u.id
AND p.photo != ''
AND (u.ownership=1 OR u.stamp=1)
ORDER BY Rand) t
LIMIT 18
Order by Rand()
は、大きなテーブルでは非常に遅く、
PHPスクリプトで次の回避策を見つけました。
Select min(id) as min, max(id) as max from table;
次に、PHPでランダムに実行します
$Rand = Rand($min, $max);
それから
'Select * from table where id>'.$Rand.' limit 1';
かなり速いようです。..
私が使用しているソリューションは、以下のリンクにも掲載されています。 MySQLのORDER BY Rand()関数を最適化するにはどうすればよいですか?
ユーザーテーブルはプロファイルテーブルよりも大きくなると想定していますが、そうでない場合は1対1のカーディナリティになります。
その場合、プロファイルテーブルに参加する前に、まずユーザーテーブルでランダムに選択します。
最初に選択を行います:
SELECT *
FROM users
WHERE users.ownership = 1 OR users.stamp = 1
次に、このプールから、計算された確率からランダムな行を選択します。テーブルにM行があり、N個のランダム行を選択する場合、ランダム選択の確率はN/Mである必要があります。したがって:
SELECT *
FROM
(
SELECT *
FROM users
WHERE users.ownership = 1 OR users.stamp = 1
) as U
WHERE
Rand() <= $limitCount / (SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1)
Nは$ limitCountで、Mはテーブルの行数を計算するサブクエリです。ただし、確率に取り組んでいるので、返される行の$ limitCountよりも少ないLESSを持つことができます。したがって、Nに係数を掛けて、ランダムプールサイズを増やす必要があります。
すなわち:
SELECT*
FROM
(
SELECT *
FROM users
WHERE users.ownership = 1 OR users.stamp = 1
) as U
WHERE
Rand() <= $limitCount * $factor / (SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1)
通常、$ factor = 2に設定します。係数を低い値に設定して、ランダムプールサイズをさらに小さくすることができます(例:1.5)。
この時点で、Mサイズのテーブルはすでに約2Nサイズに制限されています。ここから、JOINを実行してからLIMITを実行できます。
SELECT *
FROM
(
SELECT *
FROM
(
SELECT *
FROM users
WHERE users.ownership = 1 OR users.stamp = 1
) as U
WHERE
Rand() <= $limitCount * $factor / (SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1)
) as randUser
JOIN profiles
ON randUser.id = profiles.memberid AND profiles.photo != ''
LIMIT $limitCount
大きなテーブルでは、このクエリは通常のORDER by Rand()クエリよりも優れています。
お役に立てれば!