MySQL的ORDER BY兰德()是如何工作的?

bis0qfac  于 2023-10-15  发布在  Mysql
关注(0)|答案(7)|浏览(181)

我一直在研究和测试如何在MySQL中进行快速随机选择。在这个过程中,我遇到了一些意想不到的结果,现在我不完全确定我知道兰德()是如何工作的。
我一直认为,当你对表执行ORDER BY兰德()时,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兰德()需要30-40秒,而他的查询只需要0.1秒。他在博客中解释了这是如何运作的,所以我将跳过这一点,最后转移到奇怪的事情。
我的表是一个普通的表,主键是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*/

我有点期待看到所有三个查询的时间大致相同,因为我总是在一个列上排序。但由于某种原因,这并没有发生。如果你对此有什么想法,请告诉我.我有一个项目,我需要做快速订购兰德(),我个人更喜欢使用

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。
谢谢你的时间!

h4cxqtbf

h4cxqtbf1#

虽然没有“兰德()快速排序”这样的东西,但对于您的特定任务有一个变通方案。

获取任意随机行,你可以像这个德国博主那样做: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兰德()LIMIT 1;/*30-40秒 */
SELECT id FROM table ORDER BY兰德()LIMIT 1;/*0.25秒 */
SELECT id,username FROM table ORDER BY兰德()LIMIT 1;/*90秒 */
我有点期待看到所有三个查询的时间大致相同,因为我总是在一个列上排序。但由于某种原因,这并没有发生。如果你对此有什么想法,请告诉我.
这可能与索引有关。id是索引的,可以快速访问,而将username添加到结果中意味着它需要从每行读取并将其放入内存表中。对于*,它也必须将所有内容读入内存,但它不需要在数据文件中跳转,这意味着没有时间浪费。
只有当存在可变长度列(varchar/text)时,这才有区别,这意味着它必须检查长度,然后跳过该长度,而不是在每行之间跳过设置的长度(或0)。

uurv41yg

uurv41yg2#

这可能与索引有关。id是索引的,可以快速访问,而将username添加到结果中意味着它需要从每一行读取并将其放入内存表中。使用 *,它也必须将所有内容读入内存,但它不需要在数据文件中跳转,这意味着没有时间浪费。只有当存在可变长度列时,这才有区别,这意味着它必须检查长度,然后跳过该长度,而不是仅跳过每行之间的设置长度(或
实践胜于一切理论!为什么不只是检查计划?:)

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 |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
ovfsdjhp

ovfsdjhp3#

我可以告诉你为什么SELECT id FROM ...比其他两个慢得多,但我不确定,为什么SELECT id, usernameSELECT *快2-3倍。
当你有一个索引(在你的情况下是主键),并且结果只包括索引中的列时,MySQL优化器只能使用索引中的数据,甚至不会查看表本身。每一行的开销越大,您将观察到的效果就越大,因为您用纯内存操作替代了文件系统IO操作。如果你在(id,username)上有一个额外的索引,你在第三种情况下也会有类似的性能。

wfypjpf4

wfypjpf44#

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

ndasle7k

ndasle7k5#

PrimaryKey的索引。这样,“发现”更快。
如果你想要一个随机的(整行),但速度使用PrimaryKey与随机函数。。你可以试试这个(下面的代码):
使用derived-table来“查找”单个随机行的主键。然后你加入它..得到整排。

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;
wi3ka0sx

wi3ka0sx6#

使用RAND()比较慢。*也比较慢。
我无法解释的是为什么id, username*慢。
这是一个我无法复制的奇怪现象。
最快的方法是获取MAX(id)并将其存储在内存中。然后,使用您的软件拉一个随机数与它作为天花板,然后在SQL

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

如果没有行,回落到

SELECT id, username FROM table LIMIT 1;

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

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

一个小型的中型数据集。做两个选择不能更快。但是软件是有缺陷的。

zzoitvuj

zzoitvuj7#

我认为这是一个快速的解决方案:我想出了以下方法,我用这个:

-- assign a random value to each row
SELECT id, RAND() AS random_order FROM my_table;

-- sort by the plus column and limit to the required number of rows (id)
SELECT rnd.id FROM (
    SELECT id, RAND() AS random_order FROM my_table 
) AS rnd ORDER BY rnd.random_order LIMIT 9;

-- the randomly selected ID list JOIN to yourself and query the necessary data
SELECT * FROM my_table AS myt
JOIN (
    SELECT rnd.id FROM (
        SELECT id, RAND() AS random_order FROM my_table 
    ) AS rnd ORDER BY rnd.random_order LIMIT 9
) AS random_list ON random_list.id = myt.id;

-- simplified, but also fast
SELECT * FROM my_table AS myt
JOIN (
    SELECT id FROM my_table ORDER BY RAND() LIMIT 9
) AS random_list ON random_list.id = myt.id;

我在实时数据表上运行了一些查询:

Test table 1:    321 149 records, time:  0.2512 seconds
Test table 2:    865 706 records, time:  0.3070 seconds
Test table 3: 10 630 486 records, time: 52.1207 seconds

更多信息(匈牙利文):https://dev.to/benjaminhu/villamgyors-mysql-order-by-rand-lekerdezes-9pj

相关问题