【问题标题】:Returning random rows from mysql database without using rand()不使用 rand() 从 mysql 数据库返回随机行
【发布时间】:2011-03-24 19:06:00
【问题描述】:

我希望能够从数据库中提取 15 条左右的记录。我已经看到使用WHERE id = rand() 会导致我的数据库变大时出现性能问题。我见过的所有解决方案都旨在选择单个随机记录。我想得到倍数。

有人知道对大型数据库执行此操作的有效方法吗?

编辑:

进一步编辑和测试:

我在一个使用 MyISAM 的新数据库上制作了一个相当简单的表。我给了这 3 个字段:autokey(无符号自动数字键)bigdata(大块)和somemore(中等整数)。 然后我将随机数据应用于表并使用 Navicat 运行一系列查询。结果如下:

Query 1: select * from test order by rand() limit 15

 Query 2: select * 
          from 
      test 
          join 
      (select round(rand()*(select max(autokey) from test)) as val from test limit 15)                                           as rnd
      on 
          rnd.val=test.autokey;`

(我尝试了 select 和 select distinct,但没有明显区别)

和:

Query 3 (I only ran this on the second test):
SELECT  *
    FROM    (
    SELECT  @cnt := COUNT(*) + 1,
            @lim := 10
    FROM    test
    ) vars
    STRAIGHT_JOIN
    (
    SELECT  r.*,
            @lim := @lim - 1
    FROM    test r
    WHERE   (@cnt := @cnt - 1)
            AND RAND(20090301) < @lim / @cnt
    ) i
行: 查询 1: 查询 2: 查询 3: 2,060,922 2.977s 0.002s 不适用 3,043,406 5.334s 0.001s 1.260

我想做更多行,以便查看查询 3 的扩展方式,但目前,似乎明显的赢家是查询 2

在我结束这个测试并宣布答案之前,虽然我已经设置了所有这些数据和测试环境,有人可以推荐任何进一步的测试吗?

【问题讨论】:

标签: php mysql random


【解决方案1】:

试试:

select * from table order by rand() limit 15

另一种(可能更有效的方法)是加入一组随机值。如果表中有一些连续的整数键,这应该可以工作。以下是我在 postgres 中的做法(我的 MySQL 有点生疏)

select * from table join 
   (select (random()*maxid)::integer as val from generate_series(1,15)) as rnd
   on rand.val=table.id;

其中 maxid 是 table 中最高的 id。如果id 有一个索引,那么这意味着只有 15 个索引查找,所以它非常快。

更新

在 MySQL 中似乎没有 generate_series 这样的东西。我的错。我们实际上不需要它:

select * 
from 
 table 
join 
 -- this just returns 15 random numbers. 
 -- I need `table` here only to produce rows for rand()
 (select round(rand()*(select max(id) from table)) as val from table limit 15) as rnd
on 
 rnd.val=table.id;

附:如果我不想返回重复项,我可以在随机生成器表达式中使用 (select distinct [...])。

【讨论】:

    【解决方案2】:

    更新:查看this question 中接受的答案。它是纯粹的 mySQL,甚至可以处理均匀分布。

    id = rand() 或 PHP 中任何类似的东西的问题在于,您无法确定该特定 ID 是否仍然存在。因此,您需要使用LIMIT,这对于大量数据可能会变得很慢。

    作为替代方案,您可以尝试在 PHP 中使用循环。

    循环的作用是

    • 使用rand()创建一个随机整数,范围在0和数据库中的记录数之间

    • 查询数据库是否存在具有该ID的记录

    • 如果存在,将数字添加到数组中

    • 如果没有,请返回步骤 1

    • 当随机数数组包含所需数量的元素时结束循环

    这种方法可能会导致碎片表中的大量查询,但它们的执行速度应该非常快。在某些情况下,它可能LIMIT rand() 更快。

    @Luther 概述的LIMIT 方法无疑是最简单的代码。

    【讨论】:

      【解决方案3】:

      您可以对所有结果进行查询,也可以查询有限的结果,然后使用 mysqli_fetch_all 后跟:

      shuffle($a);
      $a = array_slice($a, 0, 15);
      

      【讨论】:

      • 这几乎不会节省内存 - 他在询问更多的数据。
      • 他确实说过他只想要 15 行,这取决于查询带回多少
      【解决方案4】:

      对于大型数据集做

      select * from table order by rand() limit 15
      

      可能会耗费大量时间和内存。

      如果您的数据记录碰巧被编号,您可以在编号列上放置和索引并执行

      select * from table where no >= rand() limit 15
      

      或者甚至更好地在您的应用程序中生成随机数并这样做

      select * from table where no >= $rand and no <= $rand+15
      

      如果您的数据不经常更改,则可能值得在列中添加这样的编号以提高选择效率。

      【讨论】:

      • 但这并不是真正的 15 个随机行。它是一个随机行,然后是结果中接下来的 14 行,如果 $rand 恰好落在表中的最后 15 条记录之内,它也将不起作用。
      【解决方案5】:

      假设 MySQL 支持嵌套查询并且主键上的操作很快,我会尝试类似

      select * from table where id in (select id from table order by rand() limit 15)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多