我一直在研究和测试如何在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
和其他非索引内容的公用表,例如username
,age
等。这是我正在努力解释的事情
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。
谢谢你的时间!
答案 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, username
比SELECT *
慢2-3倍。
当你有一个索引(在你的情况下是主键)并且结果只包含索引中的列时,MySQL优化器只能使用索引中的数据,甚至不会查看表本身。每行越昂贵,您将观察到的效果越大,因为您使用纯内存操作替换文件系统IO操作。如果你有一个额外的索引(id,用户名),你也会在第三种情况下有类似的表现。
答案 3 :(得分:0)
为什么不在表上添加索引id, username
,看看是否强制mysql使用索引而不仅仅是一个filesort和temp表。