【问题标题】:How does MySQL's ORDER BY RAND() work?MySQL 的 ORDER BY RAND() 是如何工作的?
【发布时间】:2011-02-09 11:02:09
【问题描述】:

我一直在研究和测试如何在 MySQL 中进行快速随机选择。在此过程中,我遇到了一些意想不到的结果,现在我不确定我是否知道 ORDER BY RAND() 的真正工作原理。

我一直认为,当您对表执行 ORDER BY RAND() 时,MySQL 会向表中添加一个新列,该列填充有随机值,然后按该列对数据进行排序,然后例如您采用随机到达的上述值。我做了很多谷歌搜索和测试,最后发现查询Jay offers in his blog确实是最快的解决方案:

SELECT * FROM Table T JOIN (SELECT CEIL(MAX(ID)*RAND()) AS ID FROM Table) AS x ON T.ID >= x.ID LIMIT 1;

虽然普通 ORDER BY RAND() 在我的测试表上需要 30-40 秒,但他的查询在 0.1 秒内完成了工作。他在博客中解释了它是如何工作的,所以我将跳过这个,最后转向奇怪的事情。

我的表是一个普通表,其中包含 PRIMARY KEY id 和其他非索引内容,如 usernameage 等。这是我正在努力解释的事情

SELECT * FROM table ORDER BY RAND() LIMIT 1; /*30-40 seconds*/
SELECT id FROM table ORDER BY RAND() LIMIT 1; /*0.25 seconds*/
SELECT id, username FROM table ORDER BY RAND() LIMIT 1; /*90 seconds*/

我有点期望看到所有三个查询的时间大致相同,因为我总是在单个列上进行排序。但由于某种原因,这并没有发生。如果您对此有任何想法,请告诉我。我有一个项目需要快速 ORDER BY RAND() 并且我个人更喜欢使用

SELECT id FROM table ORDER BY RAND() LIMIT 1;
SELECT * FROM table WHERE id=ID_FROM_PREVIOUS_QUERY LIMIT 1;

是的,它比 Jay 的方法慢,但是它更小,更容易理解。我的查询相当大,有几个 JOIN 和 WHERE 子句,虽然 Jay 的方法仍然有效,但查询变得非常大和复杂,因为我需要使用 JOINed(在他的查询中称为 x)子请求中的所有 JOIN 和 WHERE。

感谢您的宝贵时间!

【问题讨论】:

  • 作为研究的一部分,您还可以考虑在表中添加一个随机数列,而不是按 rand() 排序。然后您可以通过选择大于随机数的第一行来随机选择行。
  • @MichaelPetito 这将不起作用,因为您可能希望在每个查询中使用不同的随机“组”随机行,而您的解决方案将提供相同的“组”随机行。

标签: mysql select random


【解决方案1】:

使用RAND() 会更慢。 * 也更慢。

我无法解释为什么id, username* 慢。

这是一个我无法复制的奇怪现象。

最快的方法是获取MAX(id) 并将其存储在内存中。然后,使用你的软件拉一个随机数,以它为上限,然后在 SQL 中

SELECT id, username FROM table WHERE id > ? LIMIT 1;

如果没有行,则回退到

SELECT id, username FROM table LIMIT 1;

如果你的 MySQL 安装没有问题,你应该这样做

SELECT id, username FROM table ORDER BY RAND() LIMIT 1;

使用中小型数据集。做两个选择不能更快。但是软件有问题。

【讨论】:

    【解决方案2】:

    PrimaryKey 已编入索引。所以这些被“找到”得更快。

    如果你想要一个随机的(整行)但使用 PrimaryKey 和 Random 函数的速度..你可以试试这个(下面的代码):

    您使用派生表“查找”单个随机行的主键。然后你加入它..获得整行。

       Select * from my_thing mainTable
       JOIN
       (
          Select my_thing_key from my_thing order by RAND() LIMIT 1
        ) derived1
        on mainTable.my_thing_key = derived1.my_thing_key;
    

    【讨论】:

      【解决方案3】:

      虽然没有“通过 rand() 快速订购”之类的东西,但有针对您特定任务的解决方法。

      要获取任何单个随机行,您可以像这位德国博主那样做:http://web.archive.org/web/20200211210404/http://www.roberthartung.de/mysql-order-by-rand-a-case-study-of-alternatives/(我看不到热链接 url。如果有人看到,请随时编辑链接.)

      文本是德文的,但 SQL 代码在页面下方和大白框中,因此不难看到。

      基本上,他所做的是创建一个程序来完成获取有效行的工作。这会生成一个介于 0 和 max_id 之间的随机数,尝试获取一行,如果它不存在,请继续执行,直到找到一个。他允许通过将它们存储在临时表中来获取 x 数量的随机行,因此您可以重写该过程以更快地仅获取一行。

      这样做的缺点是,如果你删除了很多行,并且有很大的差距,那么它很可能会错过很多次,从而使其无效。

      更新:不同的执行时间

      SELECT * FROM table ORDER BY RAND() LIMIT 1; /30-40 秒/

      SELECT id FROM table ORDER BY RAND() LIMIT 1; /0.25 秒/

      SELECT id, username FROM table ORDER BY RAND() LIMIT 1; /90 秒/

      我有点期望看到所有三个查询的时间大致相同,因为我总是在单个列上进行排序。但由于某种原因,这并没有发生。如果您对此有任何想法,请告诉我。

      这可能与索引有关。 id 被索引并且可以快速访问,而将username 添加到结果中意味着它需要从每一行中读取它并将其放入内存表中。使用*,它还必须将所有内容读入内存,但不需要在数据文件中跳转,这意味着不会浪费时间寻找。

      这只有在存在可变长度列(varchar/text)时才会有所不同,这意味着它必须检查长度,然后跳过该长度,而不是只在每行之间跳过一个设定长度(或 0)。

      【讨论】:

      • 基本上,我在上面发布的 Jay 的查询几乎是相同的(当然,如果我理解正确的话),但完全是在 MySQL 中完成的。因此,如果我没记错的话,这位德国博主在 PHP 代码中手动执行了相同的操作。不幸的是,我确实从我的桌子上删除了,所以它肯定会有差距。我认为差距不会很大,但这是我无法完全控制的,所以我不能使用那种方法。
      • 不,他将其作为 mysql 程序进行。这意味着您不必为每个查询都复制它。如果它是像论坛中的“帖子”表这样的表,那就没问题了。删除会经常发生,但不足以造成问题。
      • 我明白了,谢谢,我会研究那个人提供的解决方案。但是,无论如何,我想知道为什么当我只选择id 时 ORDER BY RAND() 很快,当我选择 idusername 时非常慢,当我选择所有列时很慢?为什么时间会因我选择的列而有很大差异?
      • 这可能与索引有关。 id 被索引并且可以快速访问,而将username 添加到结果中意味着它需要从每一行中读取它并将其放入内存表中。使用*,它还必须将所有内容读入内存,但不需要在数据文件中跳转,这意味着不会浪费时间寻找。仅当存在可变长度列时才会有所不同,这意味着它必须检查长度,然后跳过该长度,而不是仅在每行之间跳过设定长度(或 0)。
      【解决方案4】:

      你为什么不在表上添加一个索引id, username,看看这是否会强制mysql使用索引而不仅仅是一个文件排序和临时表。

      【讨论】:

        【解决方案5】:

        这可能与索引有关。身份证是 索引和快速访问,而 将用户名添加到结果中,意味着 它需要从每一行中读取 并将其放入内存表中。和 * 它还必须阅读所有内容 进入内存,但不需要 跳转数据文件,意思是 没有时间浪费寻找。这 只有当有 可变长度列,这意味着 它必须检查长度,然后跳过 那个长度,而不是仅仅 跳过设定的长度(或 0) 每一行

        实践胜于所有理论!为什么不只是检查计划? :)

        mysql> explain select name from avatar order by RAND() limit 1;
        +----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
        | id | select_type | table  | type  | possible_keys | key             | key_len | ref  | rows  | Extra                                        |
        +----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
        |  1 | SIMPLE      | avatar | index | NULL          | IDX_AVATAR_NAME | 302     | NULL | 30062 | Using index; Using temporary; Using filesort |
        +----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
        1 row in set (0.00 sec)
        
        mysql> explain select * from avatar order by RAND() limit 1;
        +----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
        | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                           |
        +----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
        |  1 | SIMPLE      | avatar | ALL  | NULL          | NULL | NULL    | NULL | 30062 | Using temporary; Using filesort |
        +----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
        1 row in set (0.00 sec)
        
         mysql> explain select name, experience from avatar order by RAND() limit 1;
        +----+-------------+--------+------+--------------+------+---------+------+-------+---------------------------------+
        | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                           |
        +----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
        |  1 | SIMPLE      | avatar | ALL  | NULL          | NULL | NULL    | NULL | 30064 | Using temporary; Using filesort |
        +----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
        

        【讨论】:

          【解决方案6】:

          我可以告诉你为什么SELECT id FROM ... 比其他两个慢很多,但我不确定为什么SELECT id, usernameSELECT * 快2-3 倍。

          当您有一个索引(在您的情况下是主键)并且结果仅包含索引中的列时,MySQL 优化器只能使用索引中的数据,甚至不查看表本身。每行越昂贵,您将观察到的效果越多,因为您将文件系统 IO 操作替换为纯内存操作。如果您将在 (id, username) 上有一个额外的索引,那么您在第三种情况下也会有类似的性能。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-01-23
            • 2012-12-28
            • 2011-02-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多