【问题标题】:MySQL RAND() in WHERE clause matches a small set of rowsWHERE 子句中的 MySQL RAND() 匹配一小组行
【发布时间】:2018-02-08 13:58:42
【问题描述】:

我遇到了一个关于 MySQL 的有趣问题。当我尝试将 RAND() 函数与一些大整数相乘时,我得到的最大随机数非常小。这是我的 MySQL 查询,它应该是一个非常快速的随机查询,但它返回最大 ID 36000,即使有 4600000+ 个 ID。

SET @maxID=(SELECT MAX(id) FROM property); #it's about 4600000

SELECT * FROM property
WHERE 
downloaded_at IS NULL
AND id >= FLOOR(1 + RAND() * @maxID) #this returns max +/-36000
LIMIT 100

当我将此代码移动到普通的 SELECT 查询中时,一切都很好

SELECT FLOOR(1 + RAND() * (SELECT MAX(id) FROM property))

谁能解释一下,为什么会出现这个错误?谢谢!

修改


嗯,不知何故,当我删除downloaded_at IS NULL 时,ID 更高,但结果不再那么随机了。


我不能使用 ORDER BY RAND(),因为表太大,查询太慢,整个服务器最终在几分钟内崩溃


版本是 5.7.21-0ubuntu0.16.04.1

【问题讨论】:

  • 请注意,由于(SELECT MAX(id) FROM property) 是一个静态子查询,它只会执行一次。所以不需要使用变量。
  • 顺便说一句:我希望RAND() 只执行一次。
  • RAND() 每次都会执行,这是我的随机查询所必需的,否则它会返回行的序列,而不是随机的
  • 你所做的看起来很聪明但也很冒险,因为你只是不知道 where 子句中发生了什么。我会使用更可靠的方式。

标签: php mysql sql random


【解决方案1】:

您的随机行选择方法是有偏的...被选择的行的概率与其 id 成正比。例如,如果您有 10 行 id = 1 到 10,则 1 有 10% 的机会被选中,2 有 20% 的机会,依此类推。

此外,您的代码选择 id 小于 ~36000 的原因很明显:行(通常)按 PK 顺序处理,到找到第 100 个匹配行时,查询只处理了 id 约为 36000 的行。

现在,如果您有兴趣选择 100 个随机行,则可以改用此查询:

SELECT *
FROM property
WHERE id IN (
    SELECT id
    FROM property
    WHERE downloaded_at IS NULL
    ORDER BY RAND()
    LIMIT 100
)

或者可能是这样(粗略):

SELECT *
FROM property
WHERE id IN (
    SELECT id
    FROM property
    WHERE RAND() <= 100.0 / @maxID -- explanation below
    LIMIT 100
)

以上不涉及排序,但仍然需要扫描所有id。 100.0 与所需的行数相同,但为了确保可以添加更多行数。这应该导致每行被选中的概率相等。

【讨论】:

  • 对不起,我忘了说 - 我不能在 ORDER BY 中使用 RAND(),因为表太大而且太慢,最终导致服务器内存不足
  • @PayteR 我再次查看了您的随机化函数,您得到的 id 小于某个阈值的原因有点明显:通常按 id 顺序扫描行,并在找到第 100 个匹配时(@ 987654324@) id 在 36000 左右。ORDER BY 更好。
  • 是的,这很有趣,萨尔曼!正是这样,当我将 LIMIT 例如设置为 50 时,最大 ID 约为 18000。请编辑您的答案,将其写在答案的顶部作为答案,为什么会这样。之后我会将此答案设置为正确答案。谢谢!
  • @PayteR 查看修改后的答案。
【解决方案2】:

问题是每次评估where 子句中的条件时都会调用rand()。相反,将值放在子查询中:

SELECT p.*
FROM property p CROSS JOIN
     (SELECT FLOOR(1 + RAND() * @maxID) as idlim) x
WHERE p.downloaded_at IS NULL AND
      p.id >= x.idlim #this returns max +/-36000
LIMIT 100;

这确保rand() 函数只被调用一次。

【讨论】:

  • 谢谢@Gordon,但我每次都需要执行 RAND(),否则结果不是随机的
猜你喜欢
  • 1970-01-01
  • 2016-04-30
  • 1970-01-01
  • 2015-09-17
  • 1970-01-01
  • 2013-07-28
  • 2012-05-31
  • 1970-01-01
  • 2021-09-27
相关资源
最近更新 更多