在mysql中使用大数据集随机化结果的最快方法

Posted

技术标签:

【中文标题】在mysql中使用大数据集随机化结果的最快方法【英文标题】:Fastest way of randomising result with large dataset in mysql 【发布时间】:2018-08-27 05:54:55 【问题描述】:

我想从有大量要扫描的行的表中随机返回行

试过了:

1) select * from table order by rand() limit 1

2) select * from table where id in (select id from table order by rand() limit 1)

2 比 1 快,但在大行表上仍然太慢

更新: 查询用于实时应用程序。插入、选择和更新大约为 10/秒。所以缓存将不是理想的解决方案。这种特定情况所需的行数为 1。但也在寻找一个通用的解决方案,其中查询速度很快且所需的行数>1

【问题讨论】:

我不知道为什么有人投票结束这个问题,对我来说这很清楚而且写得很好。 你在用别的东西吗? php、python、ruby? @Erubiel 预期的结果应该完全基于 mysql 您确定查询 #2 比查询 #1 快吗?您是否以不同的顺序多次运行这两个查询?执行计划是否彼此不同?这意味着优化器在这里做得很糟糕。 @ThorstenKettner 是的 2 更快。原因很清楚,在 1 中它需要随机化整行,而在 2 中它只需要随机化主键(可能是它使用的索引,未测试) 【参考方案1】:

最快的方法是在mysql中使用prepared statement和limit

select @offset:=floor(rand()*total_rows_in_table);
PREPARE STMT FROM 'select id from table limit ?,1';
EXECUTE STMT USING @offset; 

total_rows_in_table= 表中的总行数。

比上面两个要快很多。

限制:获取超过 1 行并不是真正随机的。

【讨论】:

【参考方案2】:

在执行查询之前生成一组随机 ID(如果需要,您也可以非常快速地获得 MAX(id))。然后以id IN (your, list) 进行查询。这将使用索引仅查看您请求的 ID,因此速度非常快。

限制:如果您随机选择的某些 ID 不存在,则查询将返回较少的结果,因此您需要循环执行这些操作,直到获得足够的结果。

【讨论】:

这有另一个限制。它将基于编写代码的语言和有效生成随机数的能力【参考方案3】:

如果您可以在同一个“调用”中运行两个查询,那么您可以执行类似的操作,遗憾的是,这假设您的数据库中没有已删除的记录......如果它们在某些查询不会返回任何内容的情况下。

我用一些本地记录进行了测试,我能做的最快的就是这个……也就是说我在一个没有删除行的表上测试了它。

SET @randy = CAST(rand()*(SELECT MAX(id) FROM yourtable) as UNSIGNED);

SELECT *
FROM yourtable
WHERE id = @randy;

另一个解决方案来自修改这个问题的答案,并来自您自己的解决方案: Using variables as OFFSET in SELECT statments inside mysql's stored functions

SET @randy = CAST(rand()*(SELECT MAX(id) FROM yourtable) as UNSIGNED);
SET @q1 = CONCAT('SELECT * FROM yourtable LIMIT 1 OFFSET ', @randy);
PREPARE stmt1 FROM @q1;
EXECUTE stmt1;

【讨论】:

这个想法很好,但我永远不会依赖 ID 是连续的。您甚至不必删除记录即可创建空白。插入时的任何错误都会回滚事务并关闭应该使用的 ID。 你说得对,先生,也许用 MAX(ID) 和一个 WHILE 这个结果为 NULL 包装?顺便说一句,我不知道该怎么写...与此同时,我将用 MAX(id) 更正我的答案..我还删除了 SELECT * 解释...虽然它缩短了时间,但不是当您只检索一列时相关 如何更快?两者都使用相同的语句。只是语法不同 让我再跑几次 在查询中添加 sql_no_cache 以在 testinf 之前禁用缓存结果【参考方案4】:

我想象一个表,比如说,有一百万个条目。您想随机选择一行,因此每行生成一个随机数,即一百万个随机数,然后寻找生成数最小的行。涉及两个任务:

    生成所有这些数字 寻找最小数量

然后当然是访问记录。

如果您想要多于一行,DBMS 可以对所有条记录进行排序,然后返回 n 条记录,但希望它宁愿应用一些部分排序操作,它只检测 n 个最小数字。无论如何,这是一项相当多的任务。

我想没有彻底的方法可以规避这一点。如果你想要随机访问,这是要走的路。

但是,如果您愿意接受较少随机的结果,我建议您制作 ID 存储桶。想象一下 ID 存储桶 000000-0999999, 100000-1999999, ... 然后随机选择一个存储桶并从中选择您的随机行。好吧,诚然,这看起来不是很随机,你要么只得到旧的记录,要么只得到带有这种桶的新记录;但它说明了这项技术。

您可以使用模函数创建它们,而不是按值创建存储桶。 id % 1000 会给你 1000 个桶。第一个 ID 为 xxx000,第二个 ID 为 xxx001。这将解决新/旧记录的问题并平衡存储桶。由于 ID 只是技术性的东西,所以绘制的 ID 看起来如此相似并不重要。即使这让您感到困扰,也不要制造 1000 个桶,而要说 997 个。

现在创建一个计算列:

alter table mytable add column bucket int generated always as (id % 997) stored;

添加索引:

create index idx on mytable(bucket);

并查询数据:

select *
from mytable
where bucket = floor(rand() * 998)
order by rand()
limit 10;

这里只有大约 0.1% 的表格进入排序。所以这应该相当快。但我想这只有在一张非常大的桌子和大量的桶的情况下才值得。

该技术的缺点:

您可能没有获得所需的行数,然后您必须再次查询。 您必须明智地选择模数。如果表中只有 2000 条记录,您当然不会创建 1000 个存储桶,但可能会创建 100 个存储桶,并且一次要求的数据不会超过 10 行。 如果表格越来越大,曾经选择的数字可能不再是最佳数字,您可能需要更改它。

Rextester 链接:http://rextester.com/VDPIU7354

更新:我突然意识到,如果生成的列不是基于对 ID 的模,而是基于 RANDvalue,那么存储桶将是真正随机的:

alter table mytable add column bucket int generated always as (floor(rand() * 1000)) stored;

但 MySQL 抛出错误“生成的列 'bucket' 的表达式包含不允许的函数”。这似乎没有意义,因为使用 STORED 选项应该可以使用非确定性函数,但至少在 5.7.12 版本中这不起作用。也许在以后的版本中?

【讨论】:

以上是关于在mysql中使用大数据集随机化结果的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中随机化整数数组的最快方法

为 UICollectionView 布局散列一个非常大的数据集的最快方法是啥... NSIndexPath 太慢了

比较 2 个大型数组的最快方法 - 大数据

mysql 大数据 查询方面的测试

MySQL大数据量分页查询方法及其优化

MySQL大数据量分页查询方法及其优化