MySQL从600K行中快速select10个随机行

我怎样才能最好地写一个从总共600k中随机select10行的查询?

一个伟大的职位处理几个案例,从简单到差距,到差距不一致。

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列表中可能有空位。 请参阅文章以获取更多高级示例

 SELECT column FROM table ORDER BY RAND() LIMIT 10 

它非常简单和单行查询。

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

我得到快速查询 (约0.5秒)与一个缓慢的CPU ,select10个随机行在400K注册MySQL数据库非caching2GB大小。 在这里看到我的代码: 在MySQL中快速select随机行

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

从书籍:

使用偏移量select一个随机行

另一种避免上述替代scheme中发现的问题的技术是对数据集中的行进行计数并返回一个介于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(); 

当你不能假定连续的键值时,使用这个解决scheme,你需要确保每一行都有被选中的机会。

简单的查询,具有出色的性能 (缺口):

 SELECT * FROM tbl WHERE id IN (SELECT id FROM (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) t) 

使用了两个嵌套的子查询,因为MySQL在第一个中不支持LIMIT。

这很快,因为sorting阶段只使用索引ID列。

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

如何从表中select随机行:

从这里: selectMySQL中的随机行

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

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

那么如果你的键没有空白,而且它们都是数字,你可以计算随机数并select这些行。 但是这可能不会是这样的。

所以一个解决scheme是:

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

这将基本上确保你得到你的密钥范围内的一个随机数,然后你select下一个更好的更好。 你必须这样做10次。

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

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

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

问题是你是如何随机的。

你能解释一下吗,所以我可以给你一个好的解决scheme。

例如,我曾与一家公司有一个解决scheme,他们需要绝对的随机性非常快。 他们最终用预先填充数据库的随机值select降序,然后再次设置为不同的随机值。

如果你几乎没有更新,你也可以填写一个递增的ID,所以你没有任何的差距,只能在select之前计算随机密钥…这取决于用例!

我用这个http://jan.kneschke.de/projects/mysql/order-by-rand/张贴由Riedsio(我使用一个存储过程,返回一个或多个随机值的情况下)&#xFF1A;

  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开始( 编辑:这个列被添加到子查询在运行时创build的临时表中,不影响你的永久表):

  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; 

在文章中我可以看到他竭尽全力优化代码。 如果我的变化影响了performance,我没有任何意见,但对我来说效果很好。

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

 SELECT MAX(id) FROM table_name; 

然后将该值replace为:

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

其中max是表中的最大loggingID,n是结果集中所需的行数。 假设在loggingID中没有空白,尽pipe我怀疑它会影响结果(如果还没有尝试过)。 我也创build这个存储过程是更通用的; 传入要返回的表名和行数。 我在Windows 2008上运行MySQL 5.5.38,32GB,双3GHz E5450,在一个有17,361,264行的表上,它在〜.03 sec /〜11 sec时相当一致,可以返回1,000,000行。 (时间来自MySQL Workbench 6.1;根据您的喜好,您也可以在第二个select语句中使用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); 

这是一个可以帮助很多人的游戏改变者。

我有一个200k行的表, 有顺序的ID ,我需要selectN个随机行,所以我select根据表中最大的ID生成随机值,我创build了这个脚本来找出哪个是最快的操作:

 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方面的操作)

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

我想要指出另一种加速可能性 – caching 。 想想为什么你需要得到随机的行。 可能你想在网站上显示一些随机的post或随机广告。 如果你得到100 req / s,是真的需要每个访客得到随机行? 通常,将这些X随机行caching1秒(甚至10秒)是完全正确的。 如果在同一秒内100位唯一身份访问者获得相同的随机post,则无关紧要,因为接下来的100位访问者将获得不同的post。

当使用这个caching时,你也可以使用一些较慢的解决scheme来获取随机数据,因为不pipe你的请求是什么,它只会从MySQL每秒取一次。

如果你只有一个读取请求

将@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。 以一定的时间间隔(日,小时)重新创build表,因为它也会出现漏洞。 如果你的桌子变得很大,你也可以填充洞

    从datatable_idlist中selectl.data_id作为整体l在dt.id = l.data_id中joindatatable dt dt.id为null;

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

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

这个解决scheme用最高和最低的random_sortorder来区分“边缘行”,所以间隔(一天一次)重新排列它们。

另一个简单的解决scheme是对行进行sorting并随机取出其中一个,使用此解决scheme,您不需要在表中具有任何“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。 就像是 …

 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; 

如果有一个自动生成的id,我发现一个很好的方法是使用模运算符'%'。 例如,如果你需要10000个随机logging70000个,你可以简化这个说你需要每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更快,在我看来,比本主题中提到的其他选项更容易理解。 另外,如果写入数据库的系统批量创build一组行,则可能无法获得如您所期望的那样的随机结果。

我改进了@Riedsio的答案。 这是我可以在一个大的,均匀分布的带有间隙的表上find的最有效的查询(从具有> 2.6B行的表中获取1000个随机行进行testing)。

 (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
    • 这填补了空白。 基本上,如果你随机select一个数字在差距,它会select下一个ID。 假设差距是均匀分布的,这应该不成问题。

做联合可以帮助你把所有东西都放到1个查询中,这样你可以避免做多个查询。 它还可以让您节省计算MAX(id)的开销。 根据您的应用程序,这可能很重要或很less。

请注意,这只会得到ID并以随机顺序获取它们。 如果你想做更高级的事情,我build议你这样做:

 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 

如果你想要一个随机logging(不pipe是否在ID之间有gapes):

 PREPARE stmt FROM 'SELECT * FROM `table_name` LIMIT 1 OFFSET ?'; SET @count = (SELECT FLOOR(RAND() * COUNT(*)) FROM `table_name`); EXECUTE stmt USING @count; 

资料来源: https : //www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266

老问题,但这是我今天碰到的东西,想要select一个随机页面。 我select不使用任何答案,因为担心performance和事实,他们中的许多人在“随机”有强烈的偏见。 这是我的解决scheme(使用PHP):

Pages model:

 public static function getIDs() { $sql = "SELECT `id` FROM `pages`;"; $db = static::getDB(); $stmt = $db->query($sql); return $stmt->fetchAll(PDO::FETCH_ASSOC); } 

Pages controller:

 public function randomAction() { $pages = Pages::getIDs(); $random = $pages[rand(0, count($pages))]; $this->redirect('/' . $random['id'], 307); } 

基本上,它所做的只是从数据库中获取页面数组,然后使用PHP从返回的数组中随机select一个。

如果您需要10条logging,只需遍历数组并删除选定的logging以避免重复,然后将它们添加到单独的结果数组中。 像这样的东西:

 public static function randomAction() { $pages = Pages::getIDs(); $count = count($pages); $results = []; for($i = 0; $i < 10; $i++) { $random = rand(0, $count); $count -= 1; $results[] = $pages[$random]; unset($pages[$random]); } return $results; } 

我使用这个查询:

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

查询时间:0.016s

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

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

这是我如何做到的:

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

我喜欢它,因为不需要其他表格,写起来很简单,而且执行速度非常快。

我想这是最好的方法..

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