如何优化MySQL的ORDER BY RAND()函数?

fab*_*rik 88 mysql random performance

我想优化我的查询,所以我调查一下mysql-slow.log.

我的大多数慢查询包含ORDER BY RAND().我无法找到解决此问题的真正解决方案.Theres是MySQLPerformanceBlog的可能解决方案,但我认为这还不够.在未经优化(或经常更新,用户管理)的表上,它不起作用,或者我需要运行两个或更多查询才能选择我PHP生成的随机行.

这个问题有什么解决方案吗?

一个虚拟的例子:

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
ORDER BY
        RAND()
LIMIT 1
Run Code Online (Sandbox Code Playgroud)

Qua*_*noi 67

试试这个:

SELECT  *
FROM    (
        SELECT  @cnt := COUNT(*) + 1,
                @lim := 10
        FROM    t_random
        ) vars
STRAIGHT_JOIN
        (
        SELECT  r.*,
                @lim := @lim - 1
        FROM    t_random r
        WHERE   (@cnt := @cnt - 1)
                AND RAND(20090301) < @lim / @cnt
        ) i
Run Code Online (Sandbox Code Playgroud)

这是特别有效的MyISAM(因为它COUNT(*)是即时的),但即使InnoDB它的10效率也高于ORDER BY RAND().

这里的主要思想是我们不进行排序,而是保留两个变量并计算running probability当前步骤中要选择的行.

有关详细信息,请参阅我的博客中的这篇文章

更新:

如果您需要选择一个随机记录,请尝试以下方法:

SELECT  aco.*
FROM    (
        SELECT  minid + FLOOR((maxid - minid) * RAND()) AS randid
        FROM    (
                SELECT  MAX(ac_id) AS maxid, MIN(ac_id) AS minid
                FROM    accomodation
                ) q
        ) q2
JOIN    accomodation aco
ON      aco.ac_id =
        COALESCE
        (
        (
        SELECT  accomodation.ac_id
        FROM    accomodation
        WHERE   ac_id > randid
                AND ac_status != 'draft'
                AND ac_images != 'b:0;'
                AND NOT EXISTS
                (
                SELECT  NULL
                FROM    accomodation_category
                WHERE   acat_id = ac_category
                        AND acat_slug = 'vendeglatohely'
                )
        ORDER BY
                ac_id
        LIMIT   1
        ),
        (
        SELECT  accomodation.ac_id
        FROM    accomodation
        WHERE   ac_status != 'draft'
                AND ac_images != 'b:0;'
                AND NOT EXISTS
                (
                SELECT  NULL
                FROM    accomodation_category
                WHERE   acat_id = ac_category
                        AND acat_slug = 'vendeglatohely'
                )
        ORDER BY
                ac_id
        LIMIT   1
        )
        )
Run Code Online (Sandbox Code Playgroud)

这假设您ac_id的分布或多或少均匀分布.


Dis*_*oat 13

这取决于你需要的随机性.您链接的解决方案非常适合IMO.除非你在ID字段中有很大的空白,否则它仍然是随机的.

但是,您应该能够在一个查询中使用它(用于选择单个值):

SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*MAX(id)) LIMIT 1
Run Code Online (Sandbox Code Playgroud)

其他方案:

  • 添加一个名为random表的永久浮点字段,并用随机数填充它.然后,您可以在PHP中生成一个随机数"SELECT ... WHERE rnd > $random"
  • 获取整个ID列表并将其缓存在文本文件中.阅读文件并从中挑选一个随机ID.
  • 将查询结果缓存为HTML并保留几个小时.

  • `SELECT [fields] FROM [table] WHERE id> = FLOOR(1 + RAND()*(SELECT MAX(id)FROM [table]))LIMIT 1`似乎对我有用了 (11认同)
  • 它只是我或这个查询不起作用?我尝试了几个变种,他们都扔"无效使用组功能".. (8认同)