MySQL:ORDER BY RAND() 的替代方案

Posted

技术标签:

【中文标题】MySQL:ORDER BY RAND() 的替代方案【英文标题】:MySQL: Alternatives to ORDER BY RAND() 【发布时间】:2010-12-21 20:27:48 【问题描述】:

我已经阅读了 mysqlORDER 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 &gt; '.$rand.' 可能什么也不返回,所以WHERE id &gt;= '.$rand.' 会更好 索引中的空白可能会导致结果有偏差。如果ids 有 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() 的替代方案的主要内容,如果未能解决你的问题,请参考以下文章

php order by rand()问题

MySQL 查询优化与 group by 和 order by rand

改进MySQL Order By Rand()的低效率

ORDER BY RAND()函数在mysql中执行需要很长时间[重复]

MySQL ORDER BY rand(),名称为 ASC

PHP,MySQL - 结果数组洗牌会比“select ... order by rand()”更快吗?