MySQL的ORDER BY RAND()如何工作?

时间:2010-04-18 19:54:38

标签: mysql select random

我一直在研究和测试如何在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。

谢谢你的时间!

4 个答案:

答案 0 :(得分:14)

虽然没有“按rand()快速订购”这样的事情,但是针对您的具体任务有一种解决方法。

要获得任意一行,您可以像德国博客那样做:http://www.roberthartung.de/mysql-order-by-rand-a-case-study-of-alternatives/(我看不到热门链接网址。如果有人看到,请随时修改链接。)

该文本是德文版,但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)。

答案 1 :(得分:2)

  

它可能与索引有关。 id是   索引和快速访问,而   在结果中添加用户名,意味着   它需要从每一行读取   并把它放在内存表中。同   *它还必须阅读所有内容   进入记忆,但它不需要   跳转数据文件,意思是   没有时间失去寻求。这个   只有有了才有所作为   变长列,意思是   它必须检查长度,然后跳过   这个长度,而不仅仅是   跳过之间的设定长度(或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 |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

为什么不在表上添加索引id, username,看看是否强制mysql使用索引而不仅仅是一个filesort和temp表。