我目前正在开发一个多线程程序(用Java),需要在数据库中选择随机行,以便更新它们。这很好用但我开始遇到一些关于SELECT请求的性能问题。
我在找到这个网站之前尝试了多种解决方案:
http://jan.kneschke.de/projects/mysql/order-by-rand/
我尝试了以下解决方案:
SELECT * FROM Table
JOIN (SELECT FLOOR( COUNT(*) * RAND() ) AS Random FROM Table)
AS R ON Table.ID > R.Random
WHERE Table.FOREIGNKEY_ID IS NULL
LIMIT 1;
它只选择生成的随机ID号下面的一行。这工作得很好(150k行的每个请求平均不到100ms)。但是在我的程序进程之后,FOREIGNKEY_ID将不再为NULL(它将以某个值更新)。
问题是,我的SELECT会忘记"某些行的ID低于随机生成的ID,我无法处理它们。
所以我尝试调整我的请求,这样做:
SELECT * FROM Table
JOIN (SELECT FLOOR(
(SELECT COUNT(id) FROM Table WHERE FOREIGNKEY_ID IS NULL) * RAND() )
AS Random FROM Table)
AS R ON Table.ID > R.Random
WHERE Table.FOREIGNKEY_ID IS NULL
LIMIT 1;
根据该请求,不再有跳过某些行的问题,但性能正在急剧下降(150k行上每个请求的平均值为1)。
当我还有很多要处理的行时,我可以简单地执行快速的那个,当它只剩下几行时切换到慢速行,但它会变成一个“脏”的行。修复代码,我更喜欢优雅的SQL请求,可以完成这项工作。
感谢您的帮助,如果我不清楚或您是否需要更多详细信息,请与我们联系。
答案 0 :(得分:1)
你的身份证可能会包含差距。任何与COUNT(*)
一起使用的内容都无法找到所有ID。
包含ID&{39} 1,2,3,10,11,12,13
的记录的表只有7条记录。使用COUNT(*)
随机执行通常会导致错过,因为记录4,5和6不存在,然后它将选择最近的ID 3
。这不仅是不平衡的(它会经常选择3
),但它也永远不会选择记录10-13。
为了获得公平统一的随机选择记录,我建议先加载表格的ID。即使对于150k行,加载一组integer
id也不会占用大量内存(<1 MB):
SELECT id FROM table;
然后,您可以使用Collections.shuffle
之类的函数随机化ID的顺序。要获取其余数据,您可以一次选择一个记录,例如一次选择10个记录:
SELECT * FROM table WHERE id = :id
或者:
SELECT * FROM table WHERE id IN (:id1, :id2, :id3)
如果id
列有索引,这应该很快,并且它会为您提供适当的随机分布。
答案 1 :(得分:1)
为了让您的方法更常用,您需要max(id)
而不是count(*)
:
SELECT t.*
FROM Table t JOIN
(SELECT FLOOR(MAX(id) * RAND() ) AS Random FROM Table) r
ON t.ID > R.Random
WHERE t.FOREIGNKEY_ID IS NULL
ORDER BY t.ID
LIMIT 1;
通常会添加ORDER BY
以确保返回“下一个”ID。理论上,MySQL总是可以返回表中的最大id。
问题是ID的差距。并且,很容易创建您永远不会得到随机数的分布。 。 。说这四个ID是1
,2
,3
,1000
。您的方法永远不会获得1000000
。以上几乎总能得到它。
对您的问题最简单的解决方案可能是多次运行第一个查询,直到获得有效行。下一个建议是(FOREIGNKEY_ID, ID)
上的索引,子查询可以使用该索引。这可能会加快查询速度。
我倾向于更倾向于这些方面:
SELECT t.id
FROM Table t
WHERE t.FOREIGNKEY_ID IS NULL AND
RAND() < 1.0 / 1000
ORDER BY RAND()
LIMIT 1;
WHERE
子句的目的是减少音量,因此ORDER BY
不会花费太多时间。
不幸的是,这需要扫描表,因此您可能无法在150k表上获得100 ms范围内的响应。您可以使用t(FOREIGNKEY_ID, ID)
上的索引将其减少为索引扫描。
编辑:
如果你想要一个合理的均匀分布和性能的机会,随着表变大而不会增加,这是另一个想法,唉 - 需要一个触发器。
向名为random
的表中添加一个新列,该表使用rand(). Build an index on
random`初始化。然后运行查询,例如:
select t.*
from ((select t.*
from t
where random >= @random
order by random
limit 10
) union all
(select t.*
from t
where random < @random
order by random desc
limit 10
)
) t
order by rand();
limit 1;
这个想法是子查询可以使用索引来选择一组20行,这些行非常随意 - 在所选点之前和之后10行。然后对行进行排序(一些开销,您可以使用limit
号控制)。这些是随机的并返回。
这个想法是,如果你选择随机数,就会有任意间隙,这些会使所选数字不均匀。但是,通过在值周围采用更大的样本,则选择任何一个值的概率应接近均匀分布。均匀性仍然会产生边缘效应,但对于大量数据来说这些应该是微不足道的。
答案 2 :(得分:0)
如果可以使用预备语句,那么这应该有效:
SELECT @skip := Floor(Rand() * Count(*)) FROM Table WHERE FOREIGNKEY_ID IS NULL;
PREPARE STMT FROM 'SELECT * FROM Table WHERE FOREIGNKEY_ID IS NULL LIMIT ?, 1';
EXECUTE STMT USING @skip;