(PHP) MySQL 随机行大表,具有 order by 和一定范围

Posted

技术标签:

【中文标题】(PHP) MySQL 随机行大表,具有 order by 和一定范围【英文标题】:(PHP) MySQL random rows big table with order by and certain range 【发布时间】:2013-05-20 23:18:41 【问题描述】:

我有这张桌子:

person_id int(10) pk
fid bigint(20) unique
points int(6) index
birthday date index
4 FK columns int(6)
ENGINE = MyISAM

重要信息:该表包含超过 800 万行并且正在快速增长(目前每天 150 万)

我想要什么:当我在点上排序表格时,在一定范围内随机选择 4 行

我现在怎么做:php 中我随机化了某个范围,假设这给了我 20% 的低范围和 30% 的高范围。接下来我计算(*)表中的行数。在我确定最低行数后:表数/100 * 低范围。高范围也一样。在我使用 rand(lowest_row,highest_row) 计算随机行之后,这给了我一个范围内的行号。最后我选择随机行:

SELECT * FROM `persons` WHERE points > 0 ORDER BY points desc LIMIT $random_offset, 1;

点 > 0 在查询中,因为我只想要至少 1 点的随机数。

上面的查询大约需要 1.5 秒才能运行,但由于我需要 4 行,所以需要 6 多秒,这对我来说太慢了。我认为按点排序最耗时,所以我正在考虑制作表格的视图,但我真的没有视图的经验,所以你怎么看?视图是一个不错的选择还是有更好的解决方案?

添加:

我忘了说重要的是所有行都有相同的被选中的机会。

谢谢,感谢所有帮助! :)

凯文

【问题讨论】:

【参考方案1】:

您的查询非常慢,并且会以指数级速度变慢,因为在这里使用LIMIT 会强制它进行全表排序,然后进行全表扫描,以获得结果。相反,您也应该在 PHP 端执行此操作(LIMIT 的这种“滥用”实际上是它是非标准 SQL 的原因,例如 MSSQL 和 Oracle 不支持它)。

首先确保points 上有索引。这将使select max(points), min(points) from persons 成为一个立即返回的查询。接下来,您可以从这 2 个结果中确定点范围,并使用 rand() 确定请求范围内的 4 个点。然后对每个结果重复:

SELECT * FROM persons WHERE points < $myValue ORDER BY points DESC LIMIT 1

由于它只需要检索一行,并且可以通过索引确定哪一行,因此执行时间也是毫秒级。

【讨论】:

如果他可以确定 max/min 查询后的 4 个点,那么他可以使用“WHERE points IN($rand_pt1,$rand_pt2,$rand_pt3,$rand_pt4)”的单个查询? 如果有完整的点分布,这可能会起作用。然而,PHP 中的随机化很可能会产生数据库中不存在的分数,因此使用 IN 语法可能不会产生 4 个结果(甚至根本没有)。这就是我提供 &lt; ... LIMIT 1 构造的原因。 非常感谢您的回答,但我还没有完全理解。您正在选择最小点和最大点作为范围,但我需要一个取决于我认为的行计数器的范围,或者它也可以与点一起使用?例如,当按点排序时,我希望在表的前 10% 中有 4 个随机行。 在行数上随机化实际上是不可行的,除非它是一个永不删除的表(然后您可以基于该前提安全地使用 AI PK)。至于任何一种随机化——不要在这种表大小的数据库中进行,它最终会杀死你的 mysql。寻找 PHP 方面的替代方案,并接受它们可能不太精确。 @NielsKeurentjes 再次感谢您的解释。我对 MySQL 很陌生,但您的查询将返回 $myValue 下方的第一行。在表格中,许多行具有相同数量的点,并且每一行都有相同的被选中的机会是非常重要的。对不起,我应该以前说过。你有什么解决办法吗?【参考方案2】:

视图不会对您的表现有任何帮助。我的建议是简单地运行:

SELECT * FROM `persons` WHERE points BETWEEN ? AND ?

确保你有一个关于点的索引。此外,如果适用,您应该仅将 * 替换为您关心的字段。这里是课程? 代表搜索的上限和下限。

然后,您可以使用mysqli_num_rows()(或基于您选择的数据库库的类似方法)确定结果集中返回的行数。

您现在拥有符合条件的总行数。然后,您可以轻松地计算结果范围内的 4 个随机数,并使用mysqli_data_seek() 或类似方法直接转到随机偏移处的记录并从中获取您想要的值。

把它们放在一起:

$result = mysqli_query($db_conn, $sql); // here $sql is your SQL query
$num_records = 4; // your number of records to return
$num_rows = mysqli_num_rows($result);
$rows = array();

while ($i = 0; $i < $num_records; $i++) 
   $random_offset = rand(0, $num_rows - 1);
   mysqli_data_seek($result, $random_offset);
   $rows[] = mysqli_fetch_object($result);


mysqli_free_result($result);

【讨论】:

考虑到表的大小,这不是一个实用的解决方案,OP 说 “该表包含超过 800 万行并且正在快速增长(目前每天 150 万)” .当您的解决方案必须选择 1 亿行并从中选择一些随机结果时,它也会让数据库陷入困境。 非常感谢您的回答。两个问题。行数组中的所有行都是唯一的吗?其次,您认为使用 max(points) 代替 count(*) 并确定点上的范围而不是 row_counter 是否可能/聪明。正如您所建议的那样,这将快得多,但是如果我创建点范围并且在某个范围内只有几行而在其他范围内有更多的行,那么这些行将在随机函数中显示得更多。有没有办法以某种方式解决这个问题,所以所有行都有相同的变化? 在我的问题中,我忘了说所有行都有相同的被选中机会是非常重要的。

以上是关于(PHP) MySQL 随机行大表,具有 order by 和一定范围的主要内容,如果未能解决你的问题,请参考以下文章

从mysql中的大表中快速选择随机行

MySQL - 从大表中选择随机行

mysql-从每个id中选择一个随机行

从 CockroachDB 中的“SELECT”返回随机行

VBA/宏根据多个条件复制随机行

在PostgreSQL中选择N个匹配条件的随机行