MySQL:ORDER BY RAND() 的替代方案
Posted
技术标签:
【中文标题】MySQL:ORDER BY RAND() 的替代方案【英文标题】:MySQL: Alternatives to ORDER BY RAND() 【发布时间】:2010-12-21 20:27:48 【问题描述】:我已经阅读了 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
【问题讨论】:
我不明白你在找什么。为什么ORDER BY RAND()
不适合?你主要关心效率吗?
是的,没错。我还没有达到你图表中显示的规模,而且我已经受到了打击。
@outis:因为它无法扩展 - 请参阅:dasprids.de/blog/2008/06/07/…
我写了一篇关于解决方案的文章大约一年去:devzone.zend.com/article/…
What is the best way to pick a random row from a table in MySQL?的可能重复
【参考方案1】:
2016 年更新
此解决方案使用索引列效果最佳。
这是一个简单的示例,经过优化的查询台标有 100,000 行。
优化:300ms
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(*)。 4s 必须是相同的数字。更改返回的数量不会对速度产生太大影响。限制 4 和限制 1000 的基准是相同的。限制 10,000 耗时长达 600 毫秒
关于加入的注意事项:仅随机化 id 比随机化整行更快。由于它必须将整行复制到内存中,然后将其随机化。连接可以是链接到子查询的任何表,以防止表扫描。
注意 where 子句:where 计数限制了随机化结果的数量。它采用一定百分比的结果并对它们进行排序,而不是对整个表进行排序。
注意子查询:如果做连接和额外的 where 子句条件,您需要将它们放在子查询和子子查询中。进行准确的计数并拉回正确的数据。
未优化:1200 毫秒
SELECT
g.*
FROM
table g
ORDER BY RAND()
LIMIT 4
优点
比 order by rand()
快 4 倍。此解决方案可用于任何具有索引列的表。
缺点
复杂的查询有点复杂。需要在子查询中维护2个代码库
【讨论】:
非常好。我一定会使用这个。 如果您将这些 id 放入缓存层 10 秒,然后让应用从缓存层中的 id 中随机选择,则拉取一系列随机 id 可能会更有用。 【参考方案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
【讨论】:
如何将RAND()
乘以一个常数值可以得到更好的分布?
@OMG Ponies:是的,但你建议 :-) 所以我的问题是:为什么 ORDER BY RAND()
比 ORDER BY RAND() * const
差?
我刚刚尝试在包含超过 50 万条记录的 InnoDB 表中选择 10 条随机记录,但与仅使用 rand() 的 order 相比,我没有看到任何显着的性能提升。
仍然需要为每一行创建一个 RAND() 值,将整个数据复制到一个临时表并对其进行排序。
这些表单不提供对ORDER BY RAND()
的任何优化。我刚刚对一百万行表进行了测试,以比较性能。平均 5 次运行的结果(丢弃第一次运行),直接ORDER BY RAND()
实际上快 11.0%。 (平均 2.70 秒对 3.04 秒)。【参考方案3】:
这不是最快的,但比常见的ORDER BY RAND()
方式更快:
ORDER BY RAND()
并没有那么慢,当您使用它只查找索引列时。您可以像这样在一个查询中获取所有 ID:
SELECT id
FROM testTable
ORDER BY RAND();
获取随机 id 序列,并将结果JOIN
发送到另一个带有其他 SELECT 或 WHERE 参数的查询:
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 倍。
【讨论】:
【参考方案4】: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';
好像挺快的……
【讨论】:
大型表格的智能解决方案。但是,如果 $rand 恰好是 max(id),WHERE id > '.$rand.'
可能什么也不返回,所以WHERE id >= '.$rand.'
会更好
索引中的空白可能会导致结果有偏差。如果id
s 有 6 条记录为 1,2,3,10,11,12,则 id 为 10 的记录更有可能被选中。【参考方案5】:
我今天遇到了这个问题,并试图将 'DISTINCT' 与 JOIN 一起使用,但我认为会出现重复项,因为 RAND 使每个 JOINed 行都不同。我摸索了一下,找到了一个可行的解决方案,如下所示:
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()
时 MySql 所做的完全相同。
我测试了它,如果你的结果集中有一个 rand 值(就像在 OMG Ponies 的解决方案中所做的那样),DISTINCT 就会被否定。所以这就是我解决这个问题的方法。【参考方案6】:
创建一列或加入带有随机数的选择(例如在 php 中生成)并按此列排序。
【讨论】:
这类似于XKCD的getRandomNumber。这将一遍又一遍地产生相同的“随机”结果,这通常不是他们想要的。【参考方案7】:我正在使用的解决方案也发布在下面的链接中: How can i optimize MySQL's ORDER BY RAND() function?
我假设您的用户表将大于您的配置文件表,如果不是,那么它是 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。因此我们应该将 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() 查询。
希望这会有所帮助!
【讨论】:
【参考方案8】:SELECT
a.id,
mod_question AS modQuestion,
mod_answers AS modAnswers
FROM
b_ask_material AS a
INNER JOIN ( SELECT id FROM b_ask_material WHERE industry = 2 ORDER BY RAND( ) LIMIT 100 ) AS b ON a.id = b.id
【讨论】:
请在您的答案中添加一些解释,以便其他人可以从中学习以上是关于MySQL:ORDER BY RAND() 的替代方案的主要内容,如果未能解决你的问题,请参考以下文章
MySQL 查询优化与 group by 和 order by rand