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

Posted

技术标签:

【中文标题】优化我的mysql语句! - 兰德()太慢了【英文标题】:Optimizing my mysql statement! - RAND() TOO SLOW 【发布时间】:2010-12-08 08:17:51 【问题描述】:

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

我需要我的语句从系统表中随机选择记录,其中该 id 尚未在当前用户 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

现在它工作得很好,只是它需要大约一分钟的时间才能开始使用它选择的记录来处理手头的工作。到这个时候,脚本通常会超时并且没有任何反应。

有人可以告诉我如何重做这个,所以完成了同样的想法,但它没有使用 rand 的 order?这似乎减慢了很多事情的速度。

谢谢!

【问题讨论】:

您的 JOIN 字段上有哪些索引?这可能是一个大瓶颈。 我不太清楚你的意思...... @Brandon 我知道这有点晚了,但如果你想要一种半简单的方式来做到这一点,你可以把它放在一个子查询中.. 有关更多详细信息,请参阅我的答案@987654321 @ How can i optimize mysql's ORDER BY RAND() function?的可能重复 【参考方案1】:

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

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 表来使用 minSystem 进行 LEFT JOIN。最后我们做 LIMIT 200。

如果您使用的是 MyISam,您可以简单地检索表大小。这消除了计算follows 表大小的额外子查询。或者,如果您的表大小不会增长得太快,您也可以硬编码分母(但这需要更多的手动维护)。

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

希望这会有所帮助(或者至少我希望你会觉得这很有趣)!

【讨论】:

【参考方案2】:

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

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

【讨论】:

谢谢,但对于这个查询的工作方式来说,这不是一个可行的选择。 为什么不呢?那篇文章中有许多不同的解决方案,我认为其中一些对你有用。您的 id 字段是自动增量字段吗?如果是这样,选择随机 id 的解决方案应该可以工作。【参考方案3】:

查询缓慢的原因是数据库需要保留所有生成的随机值及其各自数据的表示,然后才能从数据库中返回单行。您可以做的是通过使用 WHERE RAND()

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

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

【讨论】:

【参考方案4】:

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

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

或者为每一行指定一个顺序 ID,并在代码中生成随机性,然后只拉回所需的行。问题是在订购之前返回了完整的记录集。

【讨论】:

【参考方案5】:

速度慢的主要原因有两个:

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

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

以上是关于优化我的mysql语句! - 兰德()太慢了的主要内容,如果未能解决你的问题,请参考以下文章

MySQL查询太慢了

oracle 多表关联查询速度太慢了 ,求优化。。

setFormulaArray 太慢了

MySQL UNION ALL 太慢了

带有Left Join的Mysql查询太慢了

我们有个带 3 个数据集的报表跑了 2 分多钟,太慢了,有啥办法优化吗?