【问题标题】:MySQL Random rows in neighbourhood [duplicate]邻域中的MySQL随机行[重复]
【发布时间】:2013-05-16 10:49:50
【问题描述】:

我有这张表 (PERSONS) 有 2500 万行:

ID int(10) PK
points int(6) INDEX
some other columns

我想向用户显示 4 个随机的行,它们的点数彼此有些接近。经过一些搜索和调整以生成令人印象深刻的随机行后,我发现了这个查询:

SELECT person_id, points
FROM persons AS r1 JOIN
       (SELECT (RAND() *
                     (SELECT MAX(person_id)
                        FROM persons)) AS id)
        AS r2
 WHERE r1.person_id>= r2.id and points > 0
 ORDER BY r1.person_id ASC
 LIMIT 4

所以我在 PHP 中查询这个。这给了我很好的快速结果(热身时低于 0.05 秒)。但这些行实际上只是随机的(自points > 0 起至少有 1 个点)。我想显示一些有点接近的行,不必每次都如此,但假设我使用限制 50 执行此查询,然后在 PHP 中选择一个随机行和 3 个最接近的行(基于点) 在它的旁边。我认为您需要对结果进行排序,选择一个随机行并显示它之后/之前的行。但我不知道如何做到这一点,因为我对 PHP 很陌生。

任何人的建议,欢迎所有反馈:)

【问题讨论】:

  • 查询从 0.05 秒到 2.6213 秒
  • 那是优化你的表的问题,你有关于点的索引吗?
  • 是的,上面有索引
  • 子选择总是有问题的。在大多数情况下,优化器必须为主选择中的每一行运行子选择......这非常糟糕。我会做两个单独的查询。应该快得多。另一方面,您可以直接将子选择作为一列(如果它只返回一列)而不进行连接。如果您仍想将其作为子选择,这也应该会提高性能。
  • To-Be 是什么意思,能解释一下吗?

标签: php mysql sql sorting random


【解决方案1】:

在您的 points 列上建立一个索引(如果它尚不存在),然后对其执行随机化逻辑:

ALTER TABLE persons ADD INDEX (points);

SELECT   person_id, points
FROM     persons JOIN (
           SELECT RAND() * MAX(points) AS pivot
           FROM   persons
           WHERE  points > 0
         ) t ON t.pivot <= points
ORDER BY points
LIMIT    4

请注意,此方法将使用points 值范围内的均匀概率分布来选择枢轴;如果points 非常不一致,您最终可能会比其他值更频繁地以某些值为中心(从而导致看似“非随机”的结果)。

要解决这个问题,您可以通过更均匀分布的列(可能是person_id?)选择一个随机记录,然后使用该随机记录的points 值作为枢轴;即,将上述语句中的子查询替换为以下内容:

           SELECT   points AS pivot
           FROM     persons JOIN (

                      SELECT FLOOR(
                               MIN(person_id)
                             + RAND() * (MAX(person_id)-MIN(person_id))
                             ) AS random
                      FROM   persons
                      WHERE  points > 0

                    ) r ON r.random <= person_id
           WHERE    points > 0
           ORDER BY person_id
           LIMIT    1

【讨论】:

  • 您应该明确地检查 EXPLAIN 以查看它只执行一次子查询,而不是针对每一行。不过应该可以。 ;)
  • @eggyal,经过测试,我发现只有较低的点行被选中,当我有 300 行都在 1 到 100 点之间时,我只得到随机数(尝试超过 60 次),点数在1 和 20 .. 这怎么可能?
  • @eggyal,非常感谢。最后一个问题,你似乎比我更了解 SQL :) 假设我的表中有 300 人,分数在 1-100 之间。假设有 5 个人有 23 点作为示例,此查询将始终选择前 4 个,是否有可能让他们都有机会出现在随机函数中?因为我想给每个人相同百分比的展示机会。
  • @KevinVermaat:查看我的更新。
  • @eggyal 我试图将这 2 个合并到 1 个 sql 查询中,但我仍然得到某些点的第一个结果:/我做错了什么?希望我是像你一样的专家。
【解决方案2】:

我建议在 PHP 中执行两个单独的 sql 查询,而不是加入/子查询它们。在许多情况下,优化器无法简化您的查询,必须分别执行每一项。所以,在你的情况下。如果你有 1000 人,优化器将在最坏的情况下执行以下操作:

  • 获取 1000 人行
  • 为每个获得 1000 人行的人做子选择
  • 加入 1000 个人并加入行,得到 1.000.000 行
  • 全部过滤

简而言之: 1001 个查询,1.000.000 行

我的建议?

执行两个查询并且两者都没有连接或子选择(尤其是在大多数情况下组合会导致性能急剧下降)

SELECT person_id, points 
FROM persons 
ORDER BY RAND() LIMIT 1

现在将找到的点用于您的第二个查询

SELECT person_id, points, ABS(points - <POINTS FROM ABOVE>) AS distance 
FROM persons 
ORDER BY distance ASC LIMIT 4

【讨论】:

  • 您的第一个查询需要 3.0974 秒才能执行。我确实阅读了很多关于这么大的表的随机行,避免按 RAND() 排序是明智之举;)
  • 替代方案?顺便说一句,上面的解决方案仍然使用 RAND() 但作为列而不是在 where 条件内。这也可以在这里完成,这个答案的重点是将随机获取和获取其他行分成两个查询。 ;)
  • 好的,我分析了上面随机化@eggyal的方法。它似乎真的很好用,并且结合了在范围内获得 4 个项目的附加数学,它应该是要走的路。舆论似乎仍然是 PHP 在很多情况下可能会更胖,也许不是这个......
【解决方案3】:

从中删除子查询将大大提高性能和缓存,因此您可以例如获取您的 ID 列表,将其放入文件中,然后从中随机获取(例如通过从文件中读取随机行)。这将大大改善它,因为您可以查看是否将在此查询上运行 EXPLAIN 并通过更改查询以仅加载 4 个(仍然是随机的)id 的数据来比较它。

【讨论】:

  • 不,这不是真的,我确实阅读了很多关于它的内容并且它非常复杂,但是例如 ToBe 的答案需要 3 秒以上才能执行,而 @eggyal 的答案需要 0.006 秒
  • @KevinVermaat 什么不是真的?那这种方法不会优化吗?当然会的!从文件中读取随机 4 行比从数据库中获取 4 个随机 ID 快得多:)。当然,它不是“sql-way”,但它可以工作。
  • 嘿抱歉我读得太快了,你是对的,但不幸的是我需要数据库解决方案。
  • 出于安全原因,我认为许多在线应用程序不能使用文件来读取或写入数据。此外,数据库还有许多优点,例如像 FK 一样内置完整性和事务性写入。
  • @KevinVermaat 这是一个非常错误的陈述。写入/读取绝不会违反安全性,如果不读取文件,您甚至无法导入它们。即使是(现在已经死了,谢天谢地)safe_mode 也没有阻止这一点。
猜你喜欢
  • 2011-06-23
  • 2012-06-24
  • 2012-02-05
  • 2017-05-13
  • 1970-01-01
  • 2013-04-23
  • 2014-07-26
  • 1970-01-01
  • 2011-12-04
相关资源
最近更新 更多