Mysql SELECT random id上的SQL优化(带WHERE子句)

Mysql SELECT random id上的SQL优化(带WHERE子句),mysql,sql,Mysql,Sql,我目前正在开发一个多线程程序(Java),它需要选择数据库中的随机行,以便更新它们。这工作正常,但我开始遇到一些有关SELECT请求的性能问题 在找到此网站之前,我尝试了多种解决方案: 我尝试了以下解决方案: SELECT * FROM Table JOIN (SELECT FLOOR( COUNT(*) * RAND() ) AS Random FROM Table) AS R ON Table.ID > R.Random WHERE Table.FOREIGNKEY_ID IS

我目前正在开发一个多线程程序(Java),它需要选择数据库中的随机行,以便更新它们。这工作正常,但我开始遇到一些有关SELECT请求的性能问题

在找到此网站之前,我尝试了多种解决方案:

我尝试了以下解决方案:

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行上,每个请求平均1s)

我可以在仍然有很多行要处理时简单地执行快速的一个,在只剩下几行时切换到慢速的一个,但这将是代码中的“脏”修复,我更喜欢优雅的SQL请求来完成这项工作


谢谢你的帮助,如果我不清楚或者你需要更多细节,请告诉我

你的身份证可能会有漏洞。使用
COUNT(*)
的任何东西都无法找到所有ID

具有ID为
1,2,3,10,11,12,13
的记录的表只有7条记录。使用
COUNT(*)
进行随机操作通常会导致未命中,因为记录4、5和6不存在,然后它会选择最近的ID,即
3
。这不仅是不平衡的(它会太频繁地拾取
3
),而且永远不会拾取记录10-13


为了获得公平、均匀分布的随机选择记录,我建议首先加载表的ID。即使对于150k行,加载一组
integer
id也不会消耗大量内存(为了使方法更一般地工作,您希望
max(id)
而不是
count(*)

通常添加
ORDER BY
以确保返回“下一个”id。理论上,MySQL总是可以返回表中的最大id

问题是ID之间存在差距。而且,很容易创建从未获得随机数的分布…假设这四个ID是
1
2
3
1000
。您的方法将永远不会获得
1000000
。以上内容几乎总是会得到它

也许解决问题的最简单方法是多次运行第一个查询,直到得到一个有效行。下一个建议是子查询可以使用的
(FOREIGNKEY\u ID,ID)
上的索引。这可能会加快查询速度

我更倾向于以下几点:

SELECT t.id
FROM Table t 
WHERE t.FOREIGNKEY_ID IS NULL AND
      RAND() < 1.0 / 1000
ORDER BY RAND()
LIMIT 1;
其思想是子查询可以使用索引选择一组20行,这些行非常任意——在所选点之前和之后各10行。然后对这些行进行排序(一些开销,您可以使用
限制
数字进行控制)。这些行是随机的,并返回


这个想法是,如果你选择随机数,会有任意的间隔,这会使所选的数不完全一致。但是,通过在该值周围采取更大的样本,则任何一个值被选择的概率应该接近均匀分布。均匀性仍然会有边缘效应,但这些应该是一致的在大量数据上进行小修。

如果可以使用准备好的语句,那么这应该可以:

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;

您好!您想经常更新表中的数据吗?如果不想,我想您可以使用Cassandra…是的,数据将非常频繁地更新,而不仅仅是通过我的程序。感谢Gordon的相关回答,我会尝试一下,并尽快给您反馈。您好Gordon,我尝试了您推荐的第一个解决方案,速度非常快b但是,当我的id中有漏洞时,我也会遇到同样的问题。随着时间的推移,找到数据库中不存在的生成的随机id的机会会增加,因为它只会保留几行(例如,我尝试了150k行,但在进程结束后剩下2k行)也许当我得到一个NoSeult异常时,我可以计算剩余的行,以强制执行以最终检索正确的ID。你认为怎么样?@ PurPrPANDA……如果你真的想用均匀的分布来采样,你需要小心。考虑一下我给的小例子——既不是<代码> max(ID)< />也不是代码>计数(*)处理正确。最后一种方法是否接近您的性能目标?就性能而言,是的,您提供的两种方法都非常有效。我的实际问题是获得均匀分布。在您的答案下方,我有一个解决方案元素,由john16384提供,他建议我首先加载所有ID并进行洗牌在我的程序中手动删除它们。这可能会起作用,但我真的不想让代码变得如此复杂。@PurplePanda…可伸缩性和统一性很有挑战性。将ID加载到程序中会带来其他问题。它不可伸缩(排序和选择必须在某个地方进行),应用程序中的数据会过时当然,在许多情况下,这种方法可能是可行的。
SELECT * FROM table WHERE id IN (:id1, :id2, :id3)
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;
SELECT t.id
FROM Table t 
WHERE t.FOREIGNKEY_ID IS NULL AND
      RAND() < 1.0 / 1000
ORDER BY RAND()
LIMIT 1;
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;
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;