如何最好地编写一个从总共600k中随机选择10行的查询?
答案 0 :(得分:362)
一个很棒的文章处理几个案例,从简单到间隙,到有间隙的不均匀。
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列表中可能存在间隙。有关更多高级示例,请参阅文章
答案 1 :(得分:313)
SELECT column FROM table
ORDER BY RAND()
LIMIT 10
不是有效的解决方案,但有效
答案 2 :(得分:18)
非常简单的单行查询。
SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
答案 3 :(得分:17)
我正在使用慢速cpu 获得快速查询(大约0.5秒),在400K寄存器MySQL数据库非缓存2Gb大小中选择10个随机行。在这里查看我的代码:Fast selection of random rows in 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);
?>
答案 4 :(得分:15)
具有卓越性能的简单查询(适用于差距):
SELECT * FROM tbl WHERE id IN
(SELECT id FROM (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) t)
使用了两个嵌套子查询,因为MySQL在第一个中不支持LIMIT。
这很快,因为排序阶段仅使用索引ID列。
答案 5 :(得分: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();
当您无法假设连续的键值时,请使用此解决方案 你需要确保每一行都有可能被选中。
答案 6 :(得分:7)
如何从表格中选择随机行:
从这里: Select random rows in MySQL
对“表扫描”的快速改进是使用索引来获取随机ID。
SELECT *
FROM random, (
SELECT id AS sid
FROM random
ORDER BY RAND( )
LIMIT 10
) tmp
WHERE random.id = tmp.sid;
答案 7 :(得分:6)
如果您的按键没有间隙并且它们都是数字,您可以计算随机数并选择这些线。但情况可能并非如此。
所以一个解决方案如下:
SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1
这基本上可以确保您在按键范围内获得一个随机数,然后选择下一个更好的随机数。 你必须这样做10次。
然而,这并不是随机的,因为您的密钥很可能不会均匀分布。
这真的是一个很大的问题,并不容易解决满足所有要求,如果你真的想要10个随机行,MySQL的rand()是你能得到的最好的。
然而,另一种解决方案是快速的,但在随机性方面也有一个折衷,但可能更适合你。在此处阅读:How can i optimize MySQL's ORDER BY RAND() function?
问题是你需要它是多么随机。
你能解释一下,我可以给你一个很好的解决方案。
例如,我合作过的公司有一个解决方案,他们需要极快的绝对随机性。他们最终使用随机值预先填充数据库,这些随机值经过降序选择,然后再次设置为不同的随机值。
如果你几乎没有更新,你也可以填充递增的ID,这样你就没有间隙,只能在选择之前计算随机密钥......这取决于用例!
答案 8 :(得分: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开始填充连续的数字( edit: 此列添加到由此创建的临时表中运行时的子查询,不会影响您的永久表):
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;
在文章中我可以看到他竭尽全力优化代码;我不知道我的变化会影响表现,但对我来说效果很好。
答案 9 :(得分: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秒时相当一致,返回1,000,000行。 (时间来自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);
答案 10 :(得分:2)
所有最佳答案都已发布(主要是那些引用链接http://jan.kneschke.de/projects/mysql/order-by-rand/)。
我想确定另一种加速可能性 - 缓存。想想你为什么需要获得随机行。您可能希望在网站上显示一些随机帖子或随机广告。如果你得到100 req / s,是否真的需要每个访问者获得随机行?通常将这些X随机行缓存1秒(甚至10秒)完全没问题。如果100个独立访问者在同一个1秒内获得相同的随机帖子并不重要,因为下一秒另外100个访问者将获得不同的帖子。
使用此缓存时,您还可以使用一些较慢的解决方案来获取随机数据,因为无论您的req / s是什么,它都将每秒从MySQL获取一次。
答案 11 :(得分:2)
我改进了@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)
让我解开正在发生的事情。
@max := (SELECT MAX(id) FROM table)
MAX(id)
会有轻微的开销SELECT FLOOR(rand() * @max) + 1 as rand)
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
执行联合可帮助您将所有内容都放入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
答案 12 :(得分:2)
这是一个可能对许多人有帮助的游戏改变者;
我有一个包含200k行的表,包含顺序ID&#39> ,我需要选择 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
ms 0.241041183472
ms 0.216960906982
ms 根据此结果,订单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 ms (包括php端的所有操作)
答案 13 :(得分: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;
答案 14 :(得分: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;
版本:您可以保持表tmp_randorder
持久,将其称为datatable_idlist。以特定间隔(日,小时)重新创建该表,因为它也会出现漏洞。如果你的桌子变得很大,你也可以补充漏洞
选择整个l.data_id 来自datatable_idlist l 在dt.id = l.data_id上左连接数据表dt 其中dt.id为null;
版本:直接在数据表或持久性额外表datatable_sortorder
中为数据集提供random_sortorder列。索引该列。在您的应用程序中生成随机值(我将其称为$rand
)。
select l.*
from datatable l
order by abs(random_sortorder - $rand) desc
limit 1;
此解决方案区分边缘行&#39;具有最高和最低的random_sortorder,因此每隔一段时间(每天一次)重新排列它们。
答案 15 :(得分:1)
如果有自动生成的id,我觉得非常好的一种方法是使用模运算符'%'。例如,如果您需要10,000个随机记录70,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更快,在我看来比这个线程中提到的其他选项更容易理解。此外,如果写入数据库的系统批量创建行集,则可能无法获得如此随机的结果。
答案 16 :(得分:1)
如果你想要一个随机记录(无论ids之间是否有噱头):
PREPARE stmt FROM 'SELECT * FROM `table_name` LIMIT 1 OFFSET ?';
SET @count = (SELECT
FLOOR(RAND() * COUNT(*))
FROM `table_name`);
EXECUTE stmt USING @count;
答案 17 :(得分:1)
这是超快的,即使有差距,也是100%随机的。
x
可用的行数SELECT COUNT(*) as rows FROM TABLE
a_1,a_2,...,a_10
之间选取10个不同的随机数x
SELECT * FROM TABLE LIMIT 1 offset a_i
for i = 1,...,10 我在 Bill Karwin 的 SQL Antipatterns 一书中发现了这种黑客攻击。
答案 18 :(得分:1)
我知道这不是您想要的,但我会给您的答案是我在 small website 的生产中使用的。
根据您访问随机值的次数,不值得使用MySQL,因为您将无法缓存答案。我们有一个按钮可以访问随机页面,如果用户愿意,可以每分钟点击几次。这会导致大量的 MySQL 使用,至少对我来说,MySQL 是最大的优化问题。
我会采用另一种方法,您可以将答案存储在缓存中。对您的 MySQL 进行一次调用:
SELECT min(id) as min, max(id) as max FROM your_table
使用您的最小和最大 ID,您可以在您的服务器中计算一个随机数。在蟒蛇中:
random.randint(min, max)
然后,使用您的随机数,您可以在表中获得一个随机 ID:
SELECT *
FROM your_table
WHERE id >= %s
ORDER BY id ASC
LIMIT 1
在这种方法中,您对数据库进行了两次调用,但您可以将它们缓存起来并且长时间不访问数据库,从而提高了性能。请注意,如果您的桌子上有洞,这不是随机的。多于 1 行很容易,因为您可以使用 python 创建 Id 并对每一行执行一个请求,但由于它们被缓存,所以没关系。
答案 19 :(得分:0)
我已经仔细查看了所有答案,但我认为没有人提到过这种可能性,而且我不确定为什么。
如果您希望以最小的成本获得最大的简便性和速度,那么对我来说,在数据库中针对每一行存储一个随机数似乎是有意义的。只需创建一个额外的列random_number
,并将其默认设置为RAND()
。在此列上创建索引。
然后,当您要检索行时,会在代码中生成一个随机数(PHP,Perl等)并将其与列进行比较。
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
我想虽然单行非常整洁,但对于10行(如OP),您必须将其分别命名十次(或想出一个巧妙的调整方法,使我立即逃脱)
答案 20 :(得分:0)
以下内容应快速,公正且独立于id列。 但是,它不能保证返回的行数将与请求的行数匹配。
SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)
说明:假设您要从100中选择10行,那么每行都有1/10的被选择概率,这可以通过WHERE RAND() < 0.1
实现。这种方法不能保证10行。但是如果查询运行了足够多次,则每次执行的平均行数将约为10,并且表中的每一行将被均匀选择。
答案 21 :(得分: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;
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;
答案 22 :(得分:0)
我认为这是一种简单但又更快的方法,与上面的一些答案相比,我在实时服务器上对其进行了测试,并且速度更快。
SELECT * FROM `table_name` WHERE id >= (SELECT FLOOR( MAX(id) * RAND()) FROM `table_name` ) ORDER BY id LIMIT 30;
//对130行的表进行0.0014秒的操作
SELECT * FROM `table_name` WHERE 1 ORDER BY RAND() LIMIT 30
//对130行的表进行0.0042秒的操作
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 30
//对130行的表进行0.0040秒的操作
答案 23 :(得分:0)
SELECT
*
FROM
table_with_600k_rows
WHERE
RAND( )
ORDER BY
id DESC
LIMIT 30;
id是主键,按id排序, EXPLAIN table_with_600k_rows,发现该行没有扫描整个表
答案 24 :(得分:-1)
我使用此查询:
select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10
查询时间:0.016秒
答案 25 :(得分:-2)
使用以下简单查询从表中获取随机数据。
SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails
GROUP BY usr_fk_id
ORDER BY cnt ASC
LIMIT 10
答案 26 :(得分:-2)
我就是这样做的:
select *
from table_with_600k_rows
where rand() < 10/600000
limit 10
我喜欢它,因为它不需要其他表,编写起来很简单,并且执行起来非常快。
答案 27 :(得分:-4)
我想这是最好的方式..
SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no