MySQL:ORDER BY RAND的替代品()

问题描述 投票:57回答:7

我已经阅读了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 
sql mysql random sql-order-by
7个回答
27
投票

UPDATE 2016

此解决方案最适合使用索引列。

这是一个标有100,000行的简单示例和优化查询工作台。

优化:300毫秒

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

关于限制ammount的注意事项:限制4和4 /计数(*)。 4s需要是相同的数字。改变你返回的数量不会对速度造成太大影响。极限4和极限1000的基准是相同的。限制10,000使其达到600毫秒

关于连接的注意事项:随机化id只比随机化一行更快。因为它必须将整行复制到内存中然后随机化它。连接可以是链接到子查询的任何表,以防止表扫描。

note where子句:where count限制了随机化结果的数量。它需要一定比例的结果并对它们进行排序而不是整个表格。

note子查询:如果要执行连接和额外的where子句条件,则需要将它们放在子查询和子查询中。准确计数并撤回正确的数据。

不合格:1200毫秒

SELECT 
    g.*
FROM
    table g
ORDER BY RAND()
LIMIT 4

PROS

order by rand()快4倍。此解决方案可以与任何带有索引列的表一起使用。

缺点

复杂的查询有点复杂。需要在子查询中维护2个代码库


20
投票

这是另一种选择,但它仍然基于使用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

这稍微复杂一点,但随机int值的分布更好:

  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

8
投票

它不是最快的,但比常见的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()更快。我的执行时间比搜索3000个随机行快了近40倍。


1
投票

我今天遇到了这个问题并试图使用'DISTINCT'和JOINs,但我认为这是重复的,因为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

1
投票

创建一个列或连接到具有随机数的选择(例如在php中生成)并按此列排序。


1
投票

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';

似乎很快......


0
投票

我正在使用的解决方案也发布在以下链接: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

在大型表上,此查询将优于RAND()查询的正常ORDER。

希望这可以帮助!

© www.soinside.com 2019 - 2024. All rights reserved.