SELECT随机id的SQL优化(带WHERE子句)

时间:2017-04-05 13:45:23

标签: mysql sql

我目前正在开发一个多线程程序(用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请求,可以完成这项工作。

感谢您的帮助,如果我不清楚或您是否需要更多详细信息,请与我们联系。

3 个答案:

答案 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是1231000。您的方法永远不会获得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;

LIMIT in SELECT statement can be used to skip rows