优化我的mysql语句! - 兰德()太慢了

时间:2009-10-20 13:05:03

标签: sql mysql random

所以我有一张包含超过80,000条记录的表,这一条叫做系统。我还有另一个名为follow的表。

我需要我的语句从系统表中随机选择记录,其中该id尚未在当前用户标识下的下表中列出。

所以这就是我所拥有的:

    SELECT system.id, 
           system.username, 
           system.password, 
           system.followed, 
           system.isvalid, 
           follows.userid, 
           follows.systemid
      FROM system
  LEFT JOIN follows ON system.id = follows.systemid
                   AND follows.userid = 2 
      WHERE system.followed = 0 
        AND system.isvalid = 1
        AND follows.systemid IS NULL
   ORDER BY RAND()
      LIMIT 200

现在它完美无缺,除了它需要大约一分钟才能开始处理他们所选择的记录。到这个时候,脚本通常会超时,没有任何反应。

有人可以告诉我如何重做这个,所以同样的想法已经完成,但它没有使用兰德的命令?这似乎减慢了一大堆。

谢谢!

6 个答案:

答案 0 :(得分:7)

我不确定是否有一个简单的解决方案来替换您的查询,这里有一篇关于纠正此类问题的文章。

http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

答案 1 :(得分:5)

查询速度慢的原因是数据库需要保留所有生成的随机值及其各自数据的表示,然后才能返回数据库中的单行。您可以做的是通过使用WHERE RAND()<来限制首先考虑的候选行数。 x,您可以选择x作为可能至少返回所需样本数的数字。要获得真正的随机样本,您需要再次通过RAND排序或对返回的数据集进行采样。

使用此方法允许数据库以流方式处理查询,而无需构建所有数据的大型中间表示。缺点是您永远无法100%确定您获得所需的样本数量,因此您可能需要再次执行查询,直到您使用较小的样本集或逐步添加样本(确保避免重复) )直到你有你需要的样品数量。

如果您不要求查询为每个调用返回不同的结果,您还可以添加带索引的预生成随机值列并与上述技术结合使用。它允许您以公平的方式获取任意数量的样本,即使您添加或删除行,但对相同数据的相同查询当然会返回相同的结果集。

答案 2 :(得分:2)

您可以根据ID和当前时间生成一些伪随机值:

ORDER BY 37*(UNIX_TIMESTAMP() ^ system.id) & 0xffff

会混合来自id的咬合,然后只取最低16位。

答案 3 :(得分:2)

缓慢有两个主要原因:

  • SQL必须首先为每个行发出一个随机数
  • 然后必须根据此编号对行进行排序,以选择前200个

有一种技巧可以帮助解决这种情况,它需要一些准备工作,实现它的方式(以及它的相对利益)取决于你的实际用例。

==>引入一个带有“随机类别”值的额外列来过滤掉大多数行

我们的想法是让一个整数值列具有随机分配的值,一次在准备时,其值介于0到9之间(或者1到25 ......等等)。然后,需要将此列添加到查询中使用的索引。最后,通过修改查询以在此列上包含过滤器=特定值(比如3),SQL需要处理的行数减少10(或25,取决于我们在其中的不同值的数量) “随机类别”。

假设这个新列名为RandPreFilter,我们可以引入一个像

这样的索引
CREATE [UNIQUE ?] INDEX  
ON system (id, RandPreFilter)

并按如下方式更改查询

SELECT system.id
     , system.username
     , system.password
     , system.followed
     , system.isvalid
     , follows.userid
     , follows.systemid
FROM system
LEFT JOIN follows ON system.id = follows.systemid
   AND follows.userid = 2 
WHERE system.followed=0 AND system.isvalid=1
   AND follows.systemid IS NULL

   AND RandPreFilter = 1 -- or other numbers, or possibly 
        -- FLOOR(1 + RAND() * 25)
ORDER BY RAND()
LIMIT 200

答案 4 :(得分:1)

根据您的数据需要的随机性,可能值得订购数据并添加额外的“上次使用的”日期时间列,并在使用数据后对其进行更新。然后选择按上次使用的字段降序排序的前n行。

如果将其包装在准备好的语句中,您可以一次选择一个(半)随机结果,而不必担心逻辑。

或者为每一行提供一个顺序ID并在代码中生成随机性并仅回退所需的行。问题是在订购之前正在返回完整的记录集。

答案 5 :(得分:0)

也许有点晚了,但至少这里有一个额外的解决方案供将来考虑:

SELECT minSystem.id, 
    minSystem.username, 
    minSystem.password, 
    minSystem.followed, 
    minSystem.isvalid,
    randFollows.userid, 
    randFollows.systemid
FROM
(
    SELECT *
    FROM system
    WHERE system.followed = 0 AND system.isvalid = 1
) as minSystem
LEFT JOIN 
(
    SELECT * 
    FROM (
        SELECT *
        FROM follows
        WHERE follows.systemid IS NULL
    ) as minFollows
    WHERE rand() <= 200 * 1.5 / (SELECT count(*) FROM follows WHERE systemid IS NULL)
) as randFollows
ON minSystem.id = randFollows.systemid
LIMIT 200

首先,我们在系统表上执行选择以减少minSystem和minFollow临时表大小。然后我们通过计算概率从minFollows表中选择随机行。到现在为止,我们将使用相当随机的randFollows表与LES JOIN一起使用minSystem。最后我们做LIMIT 200。

如果您使用的是MyISam,则只需检索表格大小即可。这消除了额外的子查询来计算follows表大小。或者,如果您的工作台尺寸不会增长太快,您也可以对分母进行硬编码(但这需要更多的手动维护)。

如需更全面的解释,请查看我发布的解决方案: MySQL: Alternatives to ORDER BY RAND()

希望这有帮助(或者至少我希望你会发现这很有趣)!