MySQL快速从600K行中选择10个随机行

Fra*_*isc 438 mysql sql

如何最好地编写一个从总共600k中随机选择10行的查询?

Rie*_*sio 368

一个伟大的岗位处理几个案例,从简单到间隙,到不均匀的差距.

http://jan.kneschke.de/projects/mysql/order-by-rand/

对于大多数一般情况,以下是您的操作方法:

SELECT name
  FROM random AS r1 JOIN
       (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1
Run Code Online (Sandbox Code Playgroud)

这假设id的分布相等,并且id列表中可能存在间隙.有关更多高级示例,请参阅文章

  • 是的,如果您的ID存在较大差距,则随机选择最低ID的可能性远低于您的高ID.实际上,获得最大差距后第一个ID的机会实际上是最高的.因此,根据定义,这不是随机的. (48认同)
  • 在我看来,随机需要一个平等的机会获得任何结果.;) (11认同)
  • 你如何获得10个不同的随机行?你必须将限制设置为10,然后使用`mysqli_fetch_assoc($ result)`迭代10次?或者这10个结果不一定可以区分? (5认同)
  • 完整的文章解决了不平等分配和重复结果等问题. (4认同)
  • 具体来说,如果您的 ID 开头有间隙,则第一个将被选中(最小/最大-最小)。对于这种情况,一个简单的调整是 MAX()-MIN() * RAND + MIN(),这并不太慢。 (2认同)
  • 结果行是后续的。 (2认同)

Pre*_*bia 328

SELECT column FROM table
ORDER BY RAND()
LIMIT 10
Run Code Online (Sandbox Code Playgroud)

不是有效的解决方案,但有效

  • `ORDER BY RAND()`相对较慢 (134认同)
  • @zeusakm 3500字不是那么多; 问题是它爆炸超过某个点,因为MySQL必须在读取每个记录之后对所有记录进行排序; 一旦该操作击中硬盘,您就可以感受到差异. (26认同)
  • 我不想再重复一次,那是全表扫描.在大型表上,它耗费时间和内存,并且可能导致在磁盘上临时表上创建&操作,这是非常慢的. (16认同)
  • 当我在2010年采访Facebook时,他们问我如何从一个未知大小的巨大文件中选择一个随机记录,一次阅读.一旦你想出了一个想法,很容易将其概括为选择多个记录.所以是的,整理整个文件是荒谬的.同时,它非常方便.我只是用这种方法从一个包含1,000,000行的表中挑选10个随机行.当然,我不得不等一下; 但我只是想知道,这个表中的典型行看起来像...... (10认同)
  • Mateusz - 证明,`SELECT单词,转录,翻译,声音来自词汇表WHERE menu_id = $ menuId ORDER BY RAND()LIMIT 10`取0.0010,没有LIMIT 10取0.0012(在该表中3500字). (6认同)
  • 在包含1M条目的数据库上,这大约需要2秒钟 (2认同)

sni*_*ode 17

我使用缓慢的CPU获得快速查询(大约0.5秒),在400K寄存器MySQL数据库非缓存2Gb大小中选择10个随机行.在这里看到我的代码:在MySQL中快速选择随机行

<?php
$time= microtime_float();

$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);

$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
   ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
    if($id_in) $id_in.=",$id";
    else $id_in="$id";
}
mysql_free_result($rquery);

$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
    logger("$id, $url",1);
}
mysql_free_result($rquery);

$time= microtime_float()-$time;

logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>
Run Code Online (Sandbox Code Playgroud)

  • 鉴于我的记录表超过1400万,这与`ORDER BY RAND()`一样慢 (11认同)
  • @snippetsofcode在你的情况下 - 400k的行你可以使用简单的"ORDER BY rand()".你的3个查询的技巧是没用的.您可以像"SELECT id,url FROM pages WHERE id IN(SELECT id FROM pages ORDER BY rand()LIMIT 10)"那样重写它. (5认同)
  • 您的技术仍然进行表扫描.使用`FLUSH STATUS; 选择 ...; 显示会话状态如'Handler%';`看到它. (4认同)
  • 还尝试在200 req/s网页中运行该查询.并发会杀了你. (4认同)

Muh*_*eem 16

它非常简单和单行查询.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,如果桌子很大,那么`rand()的顺序是非常慢的 (16认同)
  • 如果我想保持简单,有时候接受SLOW (4认同)
  • 索引在这里没有帮助。索引对于非常具体的事情很有帮助,而这个查询不是其中之一。 (3认同)

Ali*_*Ali 16

具有出色性能的简单查询(适用于间隙):

SELECT * FROM tbl WHERE id IN 
    (SELECT id FROM (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) t)
Run Code Online (Sandbox Code Playgroud)

使用了两个嵌套子查询,因为MySQL尚未在第一个中支持LIMIT.

这很快,因为排序阶段仅使用索引ID列.

对于加权版本:https://stackoverflow.com/a/41577458/893432

  • 派生表仍然需要对整个表进行扫描和排序。 (5认同)

zlo*_*ctb 13

从书中:

使用偏移选择随机行

避免前面替代方案中发现的问题的另一种技术是计算数据集中的行并返回0和计数之间的随机数.然后在查询数据集时使用此数字作为偏移量

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();
Run Code Online (Sandbox Code Playgroud)

当您无法假设连续的键值时,请使用此解决方案,并且需要确保每行都有偶然的机会被选中.

  • 对于非常大的表,“SELECT count(*)”会变慢。 (2认同)

The*_*can 7

好吧,如果您的密钥没有间隙并且它们都是数字,您可以计算随机数并选择这些线.但情况可能并非如此.

所以一个解决方案如下:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1
Run Code Online (Sandbox Code Playgroud)

这基本上可以确保您获得钥匙范围内的随机数,然后选择更好的下一个最佳值.你必须这样做10次.

但这并不是随机的,因为你的密钥很可能不会均匀分布.

这真的是一个很大的问题,并不容易解决满足所有要求,如果你真的想要10个随机行,MySQL的rand()是你能得到的最好的.

然而,有另一个解决方案是快速的,但在随机性方面也有一个折衷,但可能更适合你.在这里阅读:如何优化MySQL的ORDER BY RAND()函数?

问题是你需要它是多么随机.

你能解释一下吗,我可以给你一个很好的解决方案.

例如,我工作过的公司有一个解决方案,他们需要非常快速的绝对随机性.他们最终使用随机值预先填充数据库,然后再次选择降序并设置为不同的随机值.

如果您几乎没有更新,您也可以填充递增ID,这样您就没有间隙,只能在选择之前计算随机密钥...这取决于用例!


小智 7

如何从表中选择随机行:

从这里: 在MySQL中选择随机行

对"表扫描"的快速改进是使用索引来获取随机ID.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;
Run Code Online (Sandbox Code Playgroud)


Mar*_*555 6

所有最佳答案都已发布(主要是引用链接http://jan.kneschke.de/projects/mysql/order-by-rand/的答案)。

我想指出另一种加速可能性——缓存。想想为什么需要获取随机行。也许您想在网站上显示一些随机帖子或随机广告。如果每秒获得 100 个请求,是否真的需要每个访问者都获得随机行?通常将这 X 个随机行缓存 1 秒(甚至 10 秒)是完全可以的。如果同一 1 秒内 100 个唯一访问者获得相同的随机帖子并不重要,因为下一秒另外 100 个访问者将获得不同的帖子集。

使用此缓存时,您还可以使用一些较慢的解决方案来获取随机数据,因为无论您的请求如何,每秒只会从 MySQL 获取一次数据。


Han*_*s Z 6

我改进了@Riedsio 的答案。这是我能在具有间隙的大型均匀分布表上找到的最有效的查询(测试是从具有 > 2.6B 行的表中获取 1000 个随机行)。

(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
Run Code Online (Sandbox Code Playgroud)

让我来解开到底发生了什么。

  1. @max := (SELECT MAX(id) FROM table)
    • 我正在计算并保存最大值。MAX(id)对于非常大的表,每次需要一行时都会有轻微的计算开销
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • 获取随机id
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • 这填补了空白。基本上,如果您在间隙中随机选择一个数字,它只会选择下一个 id。假设间隙均匀分布,这应该不是问题。

进行联合可以帮助您将所有内容放入 1 个查询中,这样您就可以避免执行多个查询。它还可以让您节省计算开销MAX(id)。根据您的应用程序,这可能很重要,也可能很重要。

请注意,这仅获取 id 并按随机顺序获取它们。如果您想做更高级的事情,我建议您这样做:

SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id
Run Code Online (Sandbox Code Playgroud)


Cod*_*key 5

我浏览了所有答案,我认为根本没有人提到这种可能性,我也不知道为什么。

如果您想要以较小的成本实现最大的简单性和速度,那么对我来说,针对数据库中的每一行存储一个随机数似乎是有意义的。只需创建一个额外的列 ,random_number并将其默认设置为RAND()。在此列上创建索引。

然后,当您想要检索行时,在代码(PHP、Perl 等)中生成一个随机数,并将其与列进行比较。

SELECT FROM tbl WHERE random_number >= :random LIMIT 1
Run Code Online (Sandbox Code Playgroud)

我想虽然对于单行来说它非常整洁,但对于像OP所要求的十行,你必须单独调用它十次(或者想出一个立即逃脱我的巧妙调整)