MySQL快速从60万行中选择10条随机行


Answers:


385

一个出色的职位,可以处理多种情况,从简单到有缺口,再到有缺口的不均匀。

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

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


52
是的,如果您的ID差距很大,那么随机选择最低ID的机会远低于高ID。实际上,获得最大差距之后的第一个ID的机会实际上是最高的。因此,根据定义,这不是随机的。
lukeocodes

6
您如何获得10个不同的随机行?您是否必须将限制设置为10,然后使用进行10次迭代mysqli_fetch_assoc($result)?还是那10个结果不一定可区分?
亚当

12
在我看来,随机要求获得任何结果的机会均等。;)
lukeocodes 2014年

4
整篇文章讨论了分布不均和结果重复等问题。
Bradd Szonye 2014年

1
具体来说,如果您在ID的开头存在空白,则第一个将在时间中被选择(最小/最大-最小)。对于这种情况,一个简单的调整就是MAX()-MIN()* RAND + MIN(),这不太慢。
代码憎恶者2014年

342
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

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


139
ORDER BY RAND()相对较慢
Mateusz Charytoniuk

7
Mateusz-证明请SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10取0.0010,没有LIMIT 10取0.0012(在该表中为3500个字)。
亚瑟·库什曼

26
@zeusakm 3500个单词并不多;问题是它爆炸超过了某个点,因为MySQL必须在读取每个记录后对所有记录进行实际排序;一旦该操作击中硬盘,您就可以感受到与众不同。
杰克

16
我不想重复自己,但是再次,这是全表扫描。在大表上,这非常浪费时间和内存,并且可能导致在磁盘上的临时表上创建&操作,这非常慢。
马特2013年

10
当我在2010年接受Facebook采访时,他们问我如何从一个未知大小的巨大文件中一次选择一个随机记录。一旦您想到了一个主意,就可以很容易地将其归纳为选择多个记录。因此,对整个文件进行排序是荒谬的。同时,它非常方便。我只是使用这种方法从具有1,000,000+行的表中选择10个随机行。当然,我不得不稍等片刻。但我只是想了解一下,此表中的典型行是什么样的……
osa

27

简单查询,具有出色的性能并且可以弥补空白

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

此查询在200K表上花费0.08s,而普通版本(SELECT * FROM tbl ORDER BY RAND()LIMIT 10)花费0.35s我的计算机上。

这是快速的,因为排序阶段仅使用索引ID列。您可以在说明中看到此行为:

SELECT * FROM tbl OR BY BY RAND()限制10: Simple Explain

SELECT * FROM tbl AS作为t1 JOIN(SELECT id from tbl ORDER BY RAND()LIMIT 10)as t2 ON t1.id = t2.id enter image description here

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


1
抱歉,我测试过!60万条记录的性能下降。
Dylan B

@DylanB我用测试更新了答案。
阿里

17

我正在使用缓慢的cpu进行快速查询(大约0.5秒),在400K寄存器中选择10个随机行,而MySQL数据库的非缓存大小为2Gb。参见我的代码:快速选择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);
?>

11
鉴于我的1400万条记录表,速度慢ORDER BY RAND()
Fabrizio 2014年

5
@snippetsofcode在您的情况下-400k行可以使用简单的“ ORDER BY rand()”。3个查询的技巧是没有用的。您可以将其重写为“ SELECT ID,URL从ID在哪里的URL(SELECT ID FROM页面,ORDER BY rand()限制10)”
Roman Podlinov

4
您的技术仍然会进行表格扫描。使用FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';才能看到它。
瑞克·詹姆斯

4
也尝试在200 req / s的网页中运行该查询。并发会杀死您。
Marki555

@RomanPodlinov相对于普通ORDER BY RAND()方法的好处是,它仅对id排序(而不是全行),因此temp表较小,但仍必须对所有id进行排序。
Marki555 2015年

16

它非常简单和单行查询。

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

20
仅供参考,order by rand()如果桌子很大,速度很慢
evilReiko '16

6
如果我想保持简单,有时可以接受SLOW

如果索引较大,则应在表上应用索引。
Muhammad Azeem '18 -10-18

1
索引在这里无济于事。索引对于非常具体的事情很有帮助,而该查询不是其中之一。
安德鲁(Andrew)

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();

当您不能假设连续的键值并且需要确保每一行都有被选中的机会时,请使用此解决方案。


1
对于非常大的表,SELECT count(*)速度变慢。
汉斯Z

7

如何从表中选择随机行:

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

对“表扫描”的快速改进是使用索引来拾取随机ID。

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

1
这对MyISAM有帮助,但对InnoDB没有帮助(假设id是clustered PRIMARY KEY)。
里克·詹姆斯

7

好吧,如果您的键中没有空格并且它们都是数字,则可以计算随机数并选择那些行。但这可能并非如此。

因此,一种解决方案是:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

这基本上可以确保您在密钥范围内得到一个随机数,然后选择下一个更大的最佳数字。您必须执行10次。

但是,这并不是真正随机的,因为您的密钥很可能不会平均分配。

这确实是一个大问题,要满足所有要求并不容易,MySQL的rand()是您真正想要的最好的10个随机行。

但是,还有另一种快速的解决方案,但在随机性方面也要取舍,但可能会更适合您。在这里阅读:如何优化MySQL的ORDER BY RAND()函数?

问题是您需要随机性如何。

您能再解释一下吗,我可以给您一个很好的解决方案。

例如,与我合作的一家公司提供了一个解决方案,其中他们需要极快的绝对随机性。最后,他们使用从大到小的随机值预填充数据库,然后将其随机设置为不同的随机值。

如果您几乎从未更新过,也可以填写一个递增的id,这样就不会有任何空白,并且可以在选择之前计算随机密钥...这取决于用例!


嗨,乔。在这种特殊情况下,键不应缺少间隙,但是随着时间的流逝,间隙可能会改变。而且,当您的答案有效时,它将生成随机的10行(假设我写的是10行),这些行是连续的,可以这么说。:) 谢谢。
Francisc

如果需要10,请使用某种并集来生成10个唯一的行。
johno 2010年

我说了什么。您需要执行10次。结合起来,合并是一种将它放在一个查询中的方法。2分钟前见我的附录。
Surrican

1
@TheSurrican,此解决方案看起来很酷,但存在严重缺陷。尝试只插入一个非常大的字符串Id,您所有的随机查询都将返回该字符串Id
Pacerier 2015年

1
FLOOR(RAND()*MAX(id))倾向于返回更大的ID。
里克·詹姆斯

3

我需要一个查询来从一个相当大的表中返回大量随机行。这就是我想出的。首先获取最大记录ID:

SELECT MAX(id) FROM table_name;

然后将该值代入:

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

其中max是表中的最大记录ID,n是您想要在结果集中的行数。假设记录id中没有空格,尽管我怀疑如果有的话会影响结果(虽然没有尝试过)。我还创建了这个存储过程,使其更加通用。传入表名和要返回的行数。我在Windows 2008、32GB,双3GHz E5450上运行MySQL 5.5.38,在具有17,361,264行的表上,在〜.03秒/〜11秒的时间内返回100万行的时间是相当一致的。(时间来自MySQL Workbench 6.1;您也可以根据自己的喜好在第二个选择语句中使用CEIL代替FLOOR)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

然后

CALL [schema name].random_rows([table name], n);

3

我改进了@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)

让我解压缩发生的事情。

  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

我需要30分随机的记录,所以我应该改变LIMIT 1,以LIMIT 30无处不在的查询
Hassaan

@Hassaan,您不应该这样,更改LIMIT 1LIMIT 30可以从表中的随机点连续获取30条记录。您应该(SELECT id FROM ....在中间放置30份该零件的副本。
汉斯Z

我已经尝试过了,但是似乎没有比Riedsio回答更有效的方法。我已经在centos 7上使用PHP 7.0.22和MariaDB在页面上每秒命中500次,Riedsio答案是我获得了500多个额外的成功响应,然后是您的答案。
哈桑(Hassaan)'17年

1
@Hassaan riedsio的答案给出了1行,这一行给出了n行,并且减少了查询的I / O开销。您也许能够更快地获得行,但是系统上的负载却更多。
汉斯Z

3

我使用了Riedsio发布的http://jan.kneschke.de/projects/mysql/order-by-rand/(我使用的是存储过程返回一个或多个随机值的情况):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO rands
           SELECT r1.id
             FROM random AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT MAX(id)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.id >= r2.id
            ORDER BY r1.id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

在文章中,他通过维护一个表(使用触发器等,请参见该文章)解决了id 差异导致的随机性问题。我正在通过向表中添加从1开始填充连续数字的另一列来解决该问题(编辑:此列将添加到由子查询在运行时创建的临时表中,不会影响您的永久表):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT COUNT(*)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.no_gaps_id >= r2.id
            ORDER BY r1.no_gaps_id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

在文章中,我看到他竭尽全力优化代码。我没有想法是否/我的更改会影响性能多少,但对我来说效果很好。


“我的想法对我/我的改变是否会影响性能有很多影响”-很多。由于@no_gaps_id可以使用no索引,因此与原始查询相比,如果您查找EXPLAIN查询,则子查询具有Using filesortUsing where(不带索引)。
Fabian Schmengler,2015年

2

这是一个游戏规则改变者,可能对许多人有帮助。

我有一个具有200k行的表,具有顺序ID,我需要选择N个随机行,因此我选择根据表中的最大ID生成随机值,我创建了此脚本来找出最快的操作:

logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();

结果是:

  • 计数:36.8418693542479毫秒
  • 最大值:0.241041183472毫秒
  • 顺序:0.216960906982毫秒

根据此结果,order desc是获取最大id的最快操作,
这是我对问题的回答:

SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
    SELECT FLOOR(RAND() * (
        SELECT id FROM tbl ORDER BY id DESC LIMIT 1
    )) n FROM tbl LIMIT 10) a

...
SELECT * FROM tbl WHERE id IN ($result);

仅供参考:要从200k表中获取10条随机行,我花了1.78 毫秒 (包括php端的所有操作)


3
建议您LIMIT稍微增加一点-可以获得重复项。
里克·詹姆斯

2

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

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

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


2

这是超快的,即使有差距,也是100%随机的。

  1. 计算x可用的行数SELECT COUNT(*) as rows FROM TABLE
  2. a_1,a_2,...,a_10从0到10 之间挑选10个不同的随机数x
  3. 像这样查询您的行:SELECT * FROM TABLE LIMIT 1 offset a_i对于i = 1,...,10

我发现这个黑客在书SQL反模式比尔Karwin


我在考虑相同的解决方案,请告诉我,它比其他方法快吗?
G. Adnane

@ G.Adnane不会比接受的答案快或慢,但是接受的答案假定id的分布相等。我无法想象有什么可以保证的方案。该解决方案在O(1)中,其中解决方案SELECT column FROM table ORDER BY RAND() LIMIT 10在O(nlog(n))中。是的,这是斋戒的解决方案,适用于任何ID分配。
亚当

否,因为在发布的可接受解决方案链接中,还有其他方法,我想知道该解决方案是否比其他解决方案快,其他方式,我们可以尝试找到其他解决方案,这就是为什么我要问的方式,+ 1为您的答案。我使用的是相同的东西
G. Adnane

在某些情况下,您想获取x行数,但是偏移量到达表的末尾,该表将返回<x行或仅返回1行。在发布我的消息之前,我没有看到您的答案,但我在这里更清楚了stackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIK似乎您选择offset后的前10行x。我认为这不是10行的随机生成。在我的答案中,您必须在步骤3中执行查询10次,即每次执行仅获得一行,并且不必担心偏移量是否位于表的末尾。
亚当

1

如果您只有一个读取请求

将@redsio的答案与临时表结合起来(600K并不多):

DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;

然后采用@redsios版本:

SELECT dt.*
FROM
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM tmp_randorder)) AS id)
        AS rnd
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

如果桌子很大,您可以在第一部分进行筛选:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

如果您有许多读取请求

  1. 版本:您可以保持表的tmp_randorder持久性,将其称为datatable_idlist。请按一定的时间间隔(天,小时)重新创建该表,因为它也会出现孔。如果桌子真的很大,您也可以补上孔

    从datatable_idlist中整体选择l.data_id l在dt.id = l.data_id上左连接数据表dt,其中dt.id为null;

  2. 版本:直接在数据表或持久性额外表中为您的数据集提供random_sortorder列datatable_sortorder。索引该列。在您的应用程序中生成一个随机值(我将其称为$rand)。

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;

此解决方案可区分具有最高和最低random_sortorder的“边缘行”,因此应按间隔(一天一次)重新排列它们。


1

另一个简单的解决方案是对行进行排名并随机获取其中的一行,使用此解决方案,您无需在表中具有任何基于“ Id”的列。

SELECT d.* FROM (
SELECT  t.*,  @rownum := @rownum + 1 AS rank
FROM mytable AS t,
    (SELECT @rownum := 0) AS r,
    (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;

您可以根据需要更改限制值,以访问所需的任意多行,但这多数是连续值。

但是,如果您不希望连续的随机值,则可以获取更大的样本并从中随机选择。就像是 ...

SELECT * FROM (
SELECT d.* FROM (
    SELECT  c.*,  @rownum := @rownum + 1 AS rank
    FROM buildbrain.`commits` AS c,
        (SELECT @rownum := 0) AS r,
        (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d 
WHERE rank >= @cnt LIMIT 10000 
) t ORDER BY RAND() LIMIT 10;

1

如果有一个自动生成的ID,我觉得很好的一种方法是使用模运算符'%'。例如,如果您需要70,000条中的10,000条随机记录,则可以通过说每7行中需要1条来简化此过程。此查询可以简化此操作:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

如果将目标行除以可用总数的结果不是整数,则将比您要求的行多一些,因此您应该添加LIMIT子句来帮助您修剪结果集,如下所示:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

这确实需要进行全面扫描,但比ORDER BY RAND要快,而且我认为比该线程中提到的其他选项更容易理解。同样,如果写入数据库的系统成批创建行集,那么您可能不会获得预期的随机结果。


2
既然我这么认为,如果每次调用都需要随机行,那就没用了。我只是在考虑从集合中获取随机行以进行一些研究的需要。我仍然认为在其他情况下取模是一件好事。您可以使用模作为首过滤波器,以降低ORDER BY RAND操作的成本。
Nicolas Cohen


1

我已经仔细研究了所有答案,但我认为根本没有人提到这种可能性,而且我不确定为什么。

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

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

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

我想虽然单行非常整洁,但是对于10行(如OP),您必须将其单独调用十次(或者想出一个巧妙的调整方法,使我立即逃脱)


这实际上是一种非常不错且有效的方法。唯一的缺点是您以空间换取速度,这在我看来似乎很合理。
Tochukwu Nkemdilim

谢谢。我有一个场景,我想要从中随机排成一列的主表有500万行,并且有很多联接,在尝试了该问题的大多数方法之后,这就是我解决的难题。对我来说,增加一栏是非常值得的权衡。
Codemonkey

0

以下内容应快速,公正且独立于id列。但是,它不能保证返回的行数将与请求的行数匹配。

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

说明:假设您要从100中选择10行,则每行具有SELECT的1/10概率,可以通过来实现WHERE RAND() < 0.1。这种方法不能保证10行。但是如果查询运行足够的次数,则每次执行的平均行数约为10,并且表中的每一行将被均匀选择。


0

您可以轻松使用带有限制的随机偏移量

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

您也可以像这样应用where子句

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

对600,000行(700MB)的表进行了测试,查询执行花费了约0.016秒的硬盘驱动器

--EDIT--
   偏移量可能接近表末尾的值,这将导致select语句返回较少的行(或者可能仅返回1行),为避免这种情况,我们可以offset在声明后再次检查,就像这样

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;

-1

我使用以下查询:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

查询时间:0.016s


具有1,2,9,15之类的PK。通过上面的查询,您将得到像4、7、14、11这样的行,它们不够用!
Junaid Atari

-2

这是我的方法:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

我喜欢它,因为它不需要其他表,它编写简单,并且执行速度非常快。


5
那是全表扫描,它不使用任何索引。对于大桌子和繁忙的环境,这是很大的不。
马特2013年

-2

使用下面的简单查询从表中获取随机数据。

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10

如果要使用任何join语句以及可以在何处使用过滤器。
MANOJ 2015年

3
您从查询的哪一部分获得随机性?
Marki555

-4

我想这是最好的方法。

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no

8
不,这是从表中获取随机行的最糟糕的方法之一。那就是全表扫描+文件排序+ tmp表=性能不佳。
马特2013年

1
除了性能之外,它还远非完全随机。您是按ID和随机数的乘积排序,而不是简单地按随机数排序,这意味着ID较低的行将倾向于在结果集中出现得更早。
Mark Amery 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.