在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 的模,而是基于 RAND
value,那么存储桶将是真正随机的:
alter table mytable add column bucket int generated always as (floor(rand() * 1000)) stored;
但 MySQL 抛出错误“生成的列 'bucket' 的表达式包含不允许的函数”。这似乎没有意义,因为使用 STORED
选项应该可以使用非确定性函数,但至少在 5.7.12 版本中这不起作用。也许在以后的版本中?
【讨论】:
以上是关于在mysql中使用大数据集随机化结果的最快方法的主要内容,如果未能解决你的问题,请参考以下文章