MySQL 从 600K 行中快速选择 10 个随机行
Posted
技术标签:
【中文标题】MySQL 从 600K 行中快速选择 10 个随机行【英文标题】:MySQL select 10 random rows from 600K rows fast 【发布时间】:2011-05-18 18:45:27 【问题描述】:我怎样才能最好地编写一个从总共 600k 中随机选择 10 行的查询?
【问题讨论】:
这里是8 techniques;也许一个会在你的情况下运作良好。 (这实际上是 5 种技术——有些没有改进。) 【参考方案1】:SELECT
*
FROM
table_with_600k_rows
WHERE
RAND( )
ORDER BY
id DESC
LIMIT 30;
id是主键,按id排序, EXPLAIN table_with_600k_rows,发现该行没有扫描整个表
【讨论】:
【参考方案2】:我知道这不是你想要的,但我会给你的答案是我在生产中使用的 small website。
根据你访问随机值的次数,不值得使用 mysql,因为你将无法缓存答案。我们有一个按钮可以访问一个随机页面,如果用户愿意,他可以每分钟点击几次。这将导致大量 MySQL 的使用,至少对我来说,MySQL 是最大的优化问题。
我会采用另一种方法,您可以将答案存储在缓存中。对您的 MySQL 进行一次调用:
SELECT min(id) as min, max(id) as max FROM your_table
使用您的最小和最大 ID,您可以在您的服务器中计算一个随机数。在python中:
random.randint(min, max)
然后,使用你的随机数,你可以在你的 Table 中获得一个随机的 Id:
SELECT *
FROM your_table
WHERE id >= %s
ORDER BY id ASC
LIMIT 1
在这种方法中,您对数据库进行了两次调用,但您可以缓存它们并且长时间不访问数据库,从而提高性能。请注意,如果您的桌子上有洞,这不是随机的。多于 1 行很容易,因为您可以使用 python 创建 Id 并为每一行执行一个请求,但由于它们被缓存,所以没关系。
【讨论】:
【参考方案3】:从书:
使用偏移量选择随机行
还有一种技术可以避免前面发现的问题 替代方法是计算数据集中的行数并返回一个随机数 介于 0 和计数之间的数字。然后使用这个数字作为偏移量 查询数据集时
$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();
当您无法假设连续的键值和 您需要确保每一行都有被选中的机会。
【讨论】:
对于非常大的表,SELECT count(*)
会变慢。【参考方案4】:
我得到 快速查询(大约 0.5 秒),使用 慢速 cpu,在 400K 寄存器 MySQL 数据库非缓存 2Gb 大小中选择 10 个随机行。在这里查看我的代码:Fast selection of random rows in MySQL
$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);
【讨论】:
鉴于我的超过 1400 万条记录表,这与ORDER BY RAND()
一样慢
@sn-psofcode 在您的情况下 - 400k 行您可以使用简单的“ORDER BY rand()”。您使用 3 个查询的技巧是没用的。您可以将其重写为“SELECT id, url FROM pages WHERE id IN (SELECT id FROM pages ORDER BY rand() LIMIT 10)”
您的技术仍然进行表扫描。使用FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';
查看。
还尝试在 200 req/s 网页中运行该查询。并发会杀了你。
@RomanPodlinov 与普通 ORDER BY RAND()
相比的好处是它只对 id 进行排序(而不是整行),因此临时表更小,但仍然必须对所有这些进行排序。【参考方案5】:
我查看了所有答案,我认为根本没有人提到这种可能性,我也不知道为什么。
如果您想以极低的成本获得最大的简单性和速度,那么对我来说,针对数据库中的每一行存储一个随机数似乎是有意义的。只需创建一个额外的列random_number
,并将其默认设置为RAND()
。在此列上创建索引。
然后,当您想要检索一行时,在您的代码(php、Perl 等)中生成一个随机数并将其与列进行比较。
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
我想虽然它对于单行来说非常整洁,但对于像 OP 要求的十行,你必须分别调用它十次(或者想出一个聪明的调整,让我立即逃脱)
【讨论】:
这实际上是一个非常好的和有效的方法。唯一的缺点是你用空间换取速度,在我看来这似乎是一个公平的交易。 谢谢。我有一个场景,我想要一个随机行的主表有 500 万行,并且有很多连接,并且在尝试了这个问题的大多数方法之后,这就是我决定的 kludge。对我来说,增加一列是非常值得的权衡。 如果你想用“LIMIT 10”获得 10 行怎么办?似乎可能性不大。 正如我在回答@edwardaa 末尾所说的那样,它只有在您想要单行时才真正有效。或者,如果您不介意多次调用它的开销。【参考方案6】:您可以轻松地使用带有限制的随机偏移量
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 秒 HDD 驱动器。
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;
【讨论】:
【参考方案7】:我认为这是一种简单而快速的方法,我在实时服务器上对其进行了测试,并与上面的一些答案进行了比较,并且速度更快。
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 秒
【讨论】:
【参考方案8】:具有卓越性能并能处理差距的简单查询:
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 ORDER BY RAND() LIMIT 10:
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id
加权版本:https://***.com/a/41577458/893432
【讨论】:
【参考方案9】:我使用了 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;
在文章中我可以看到他不遗余力地优化代码;我不知道我的更改是否/多少会影响性能,但对我来说效果很好。
【讨论】:
“我不知道我的更改是否/多少会影响性能” - 很多。对于@no_gaps_id
,不能使用索引,因此如果您查看EXPLAIN
进行查询,则与原始查询相比,子查询有Using filesort
和Using where
(无索引)。【参考方案10】:
以下内容应该是快速、公正且独立于 id 列的。 但不保证返回的行数与请求的行数一致。
SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)
说明:假设您想要 100 行中的 10 行,那么每行有 1/10 的概率被选中,这可以通过 WHERE RAND() < 0.1
实现。这种方法不保证 10 行;但如果查询运行的次数足够多,则每次执行的平均行数将在 10 左右,并且表中的每一行都会被平均选择。
【讨论】:
【参考方案11】:这速度超级快,即使有间隙也是 100% 随机的。
-
计算
x
的可用行数SELECT COUNT(*) as rows FROM TABLE
在 0 和 x
之间选择 10 个不同的随机数 a_1,a_2,...,a_10
像这样查询您的行:SELECT * FROM TABLE LIMIT 1 offset a_i
for i=1,...,10
我在Bill Karwin的SQL Antipatterns一书中发现了这个技巧。
【讨论】:
我正在考虑相同的解决方案,请告诉我,它是否比其他方法更快? @G.Adnane 它不会比接受的答案更快或更慢,但接受的答案假设 id 的分布相等。我无法想象任何可以保证这一点的情况。该解决方案在 O(1) 中,其中解决方案SELECT column FROM table ORDER BY RAND() LIMIT 10
在 O(nlog(n)) 中。所以是的,这是禁食的解决方案,它适用于任何 id 分布。
不,因为在为接受的解决方案发布的链接中,还有其他方法,我想知道这个解决方案是否比其他解决方案更快,其他方法,我们可以尝试找到另一个,这就是为什么无论如何,我要为您的答案+1。我用的是同样的东西
有一种情况,当您想要获取 x 行但偏移量到达表的末尾,这将返回 x
之后的前 10 行。我认为这不是 10 行的随机生成。在我的回答中,您必须在步骤 3 中执行 10 次查询,即每次执行只获取一行,并且不必担心偏移量是否位于表的末尾。【参考方案12】:
SELECT column FROM table
ORDER BY RAND()
LIMIT 10
不是有效的解决方案,但有效
【讨论】:
ORDER BY RAND()
比较慢
Mateusz - 请证明,SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10
需要 0.0010,没有 LIMIT 10 需要 0.0012(在该表中 3500 字)。
@zeusakm 3500 字不算多;问题是它超过了某个点,因为 MySQL 必须在读取每条记录后对所有记录进行实际排序;一旦该操作命中硬盘,您就会感受到不同。
我不想再重复一遍,但那是全表扫描。在大表上,它非常耗费时间和内存,并且可能会导致在磁盘上的临时表上创建 & 操作,这非常很慢。
当我在 2010 年接受 Facebook 采访时,他们问我如何在一次阅读中从一个未知大小的巨大文件中选择一条随机记录。一旦你想出了一个想法,就很容易将其概括为选择多条记录。所以是的,对整个文件进行排序是荒谬的。同时,它非常方便。我只是使用这种方法从一个包含 1,000,000+ 行的表中选择 10 个随机行。当然,我不得不等一下;但我只是想了解一下,这张表中的典型行是什么样的......【参考方案13】:
如果你想要一个随机记录(无论id之间是否有间隙):
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
【讨论】:
【参考方案14】:我改进了@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)
获取随机ID
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 你不应该,将LIMIT 1
更改为LIMIT 30
会从表中的随机点连续获得 30 条记录。您应该在中间有 30 个 (SELECT id FROM ....
部分的副本。
我已经尝试过,但似乎没有比Riedsio
answer 更有效。我已经尝试在 centos 7 上使用 PHP 7.0.22 和 MariaDB 每秒点击 500 次页面,Riedsio
回答我得到了 500+ 额外的成功响应然后你的回答。
@Hassaan riedsio 的回答给出了 1 行,这一行给了你 n 行,并减少了查询的 I/O 开销。您可能能够更快地获取行,但会增加系统的负载。
+1 部分将导致在少数情况下不返回结果 SELECT FLOOR(rand() * @max) + 1 as rand),如果表包含 1 条 id 为 1 的记录。Floor 将为 0 和rand 0+1 = 1. id > rand 不会返回任何东西 (1 > 1) 如果 random 返回 0.999*****。表中的示例最大 id 为 100。Floor 将为 99,rand 99+1 = 100。id > rand 不会返回任何内容 (100 > 100)【参考方案15】:
如果有自动生成的 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 更快,并且在我看来,它比该线程中提到的其他选项更易于理解。此外,如果写入数据库的系统批量创建行集,您可能不会得到预期的随机结果。
【讨论】:
现在我这么认为,如果每次调用时都需要随机行,这是没用的。我只是在考虑需要从一组中获取随机行来做一些研究。我仍然认为模数是在另一种情况下提供帮助的好东西。您可以使用模数作为第一通滤波器来降低 ORDER BY RAND 操作的成本。【参考方案16】:非常简单的单行查询。
SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
【讨论】:
仅供参考,如果表很大,order by rand()
会很慢
如果我想保持简单,有时会接受 SLOW
如果表很大,则应在表上应用索引。
索引在这里没有帮助。索引对非常具体的事情很有帮助,而这个查询不是其中之一。【参考方案17】:
另一个简单的解决方案是对行进行排名并随机获取其中一个,使用此解决方案,您不需要在表中包含任何基于“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;
【讨论】:
【参考方案18】:好吧,如果您的键没有间隙并且它们都是数字,您可以计算随机数并选择这些行。但情况可能并非如此。
所以一种解决方案如下:
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,这样你就没有间隙,并且可以在选择之前计算随机键...这取决于用例!
【讨论】:
嗨乔。在这种特殊情况下,键不应缺少间隙,但随着时间的推移,这可能会改变。虽然您的答案有效,但它会生成连续的随机 10 行(前提是我写了限制 10),可以这么说,我想要更多的随机性。 :) 谢谢。 如果您需要 10 个,请使用某种联合来生成 10 个唯一行。 同意我所说的。你需要执行 10 次。将它与 union 结合是一种将其放在一个查询中的方法。请参阅我 2 分钟前的附录。 @TheSurrican,这个解决方案看起来很酷,但有很大缺陷。尝试插入一个非常大的Id
,然后所有您的随机查询将返回那个Id
。
FLOOR(RAND()*MAX(id))
偏向于返回更大的 id。【参考方案19】:
所有最佳答案都已发布(主要是那些引用链接 http://jan.kneschke.de/projects/mysql/order-by-rand/ 的答案)。
我想指出另一种加速的可能性 - 缓存。想想为什么你需要得到随机行。可能您想在网站上显示一些随机帖子或随机广告。如果您获得 100 个请求/秒,是否真的需要每个访问者获得随机行?通常将这些 X 随机行缓存 1 秒(甚至 10 秒)是完全可以的。 100 个唯一身份访问者是否在同一 1 秒内获得相同的随机帖子并不重要,因为下一秒另外 100 个访问者将获得不同的帖子集。
使用此缓存时,您还可以使用一些较慢的解决方案来获取随机数据,因为无论您的请求/秒如何,它每秒只会从 MySQL 获取一次。
【讨论】:
【参考方案20】:这是一个可能对许多人有帮助的游戏规则改变者;
我有一个有 200k 行的表,具有顺序 ID,我需要选择 N 个随机行,因此我选择根据表,我创建了这个脚本来找出最快的操作:
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
毫秒
订单:0.216960906982
ms
根据这个结果,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 ms (包括 php 端的所有操作)
【讨论】:
建议你稍微增加LIMIT
——你可以得到重复。【参考方案21】:
使用下面的简单查询从表中获取随机数据。
SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails
GROUP BY usr_fk_id
ORDER BY cnt ASC
LIMIT 10
【讨论】:
如果你想使用任何 join 语句以及可以使用 where 过滤器。 从查询的哪个部分获得随机性?【参考方案22】:我使用这个查询:
select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10
查询时间:0.016s
【讨论】:
拥有像 1,2,9,15 这样的 PK。通过上面的查询,你会得到像 4, 7, 14, 11 这样的行是不够的!【参考方案23】:处理多种情况的出色帖子,从简单到间隙,再到带间隙的不均匀。
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 列表中可能存在间隙。有关更多高级示例,请参阅文章
【讨论】:
是的,如果您的 ID 可能存在很大差距,那么您的最低 ID 被随机挑选的机会远低于您的高 ID。事实上,最大差距后的第一个ID被选中的机会实际上是最高的。因此,根据定义,这不是随机的。 如何获得 10 个不同的随机行?您是否必须将限制设置为 10,然后使用mysqli_fetch_assoc($result)
迭代 10 次?或者这 10 个结果不一定可以区分?
在我看来,随机要求任何结果的机会均等。 ;)
整篇文章解决了分布不均和重复结果等问题。
具体来说,如果您的 ID 开头有间隔,则第一个 ID 将被选中(最小/最大-最小)时间。对于这种情况,一个简单的调整是 MAX()-MIN() * RAND + MIN(),不会太慢。【参考方案24】:
我需要一个查询来从一个相当大的表中返回大量随机行。这就是我想出的。先获取最大记录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;您也可以根据自己的喜好在第二个 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);
【讨论】:
【参考方案25】:如何从表中随机选择行:
从这里: 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;
【讨论】:
这对 MyISAM 有一些帮助,但对 InnoDB 没有帮助(假设 id 是集群的PRIMARY KEY
)。
内部查询执行全表扫描并对结果进行排序。实际上,该链接中的大多数(也许是全部)技术都涉及全面扫描。【参考方案26】:
如果您只有一个读取请求
将@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 left join datatable dt on dt.id = l.data_id 其中 dt.id 为空;
版本:直接在数据表中或在持久性额外表datatable_sortorder
中为您的数据集提供一个 random_sortorder 列。索引该列。在您的应用程序中生成一个随机值(我称之为$rand
)。
select l.*
from datatable l
order by abs(random_sortorder - $rand) desc
limit 1;
此解决方案区分具有最高和最低 random_sortorder 的“边缘行”,因此按间隔重新排列它们(每天一次)。
【讨论】:
【参考方案27】:我想这是最好的方法..
SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no
【讨论】:
见鬼,这是从表中获取随机行的最糟糕的方法之一。那是全表扫描 + 文件排序 + tmp 表 = 性能不佳。 除了性能之外,它也远非完全随机;您按 id 和随机数的乘积排序,而不是简单地按随机数排序,这意味着具有较低 id 的行将倾向于出现在结果集中的较早位置。【参考方案28】:这就是我的做法:
select *
from table_with_600k_rows
where rand() < 10/600000
limit 10
我喜欢它,因为不需要其他表,编写简单,执行速度非常快。
【讨论】:
这是全表扫描,不使用任何索引。对于大桌子和繁忙的环境,这是绝对不行的。以上是关于MySQL 从 600K 行中快速选择 10 个随机行的主要内容,如果未能解决你的问题,请参考以下文章