PHP MySQL随机排序的分页系统

20

我网站上的搜索结果排序出了问题,

当进行搜索时,在内容页面上会出现随机的结果,并且该页面还包括分页。以下是我使用的 SQL 查询语句。

SELECT * FROM table ORDER BY RAND() LIMIT 0,10;

我的问题如下:

  1. 我需要确保每次用户访问下一页时,他们已经看过的结果不再出现(在下一个查询中排除它们,以一种内存高效但仍按rand()排序的方式)。

  2. 每次访问第一页时都会出现不同的结果集,是否可以在此使用分页,或者排序始终是随机的?

  3. 我可以在MYSQL中使用种子(seed),但我不确定如何实际使用它。


将所有结果放入一个数组中,然后在那里处理它们。 - Paul Dessert
你可能需要知道的是,ORDER BY RAND() 单独使用时性能很差。它会让 MySQL 选择每一行,为每一行分配一个随机数,并根据该数字对行进行排序。不要使用它。 - user149341
6个回答

61

使用RAND(SEED)。引用文档: "如果指定了一个常量整数参数N,则将其用作种子值。" (http://dev.mysql.com/doc/refman/5.0/en/mathematical-functions.html#function_rand).

在上面的例子中,结果顺序是随机的,但始终相同。您只需更改种子即可获得新的顺序。

SELECT * FROM your_table ORDER BY RAND(351);

每当用户第一次访问结果页面时,您可以更改种子并将其存储在用户会话中。


4
这个解决方案比被接受的那个好多了。谢谢! :) - SammyK
1
是的,但是这种解决方案在大型数据库上非常缓慢。这实际上会为数据库中的每一行生成一个随机数,并在每次查询时对整个表进行排序。 - Josh Bernfeld
绝对是一个可扩展的解决方案。但无法使用索引。 - adambullmer
非常棒的解决方案!!非常感谢。 - KodeFor.Me
一个简单但易于理解的关于如何使用PHP和MySQL分页随机列表的文章:https://www.midwinter-dg.com/permalink-How_to_paginate_a_random_list_using_PHP_and_MySQL_2010-11-22.html - Ales

17

在MySQL中随机排序是一个非常棘手的问题。过去,我通常会尽可能避免这个问题。通常情况下,用户不会多次访问这样的页面集合。因此,这为您提供了避免各种令人厌恶的随机排序实现的机会,而选择一些简单但不完全随机的解决方案。

解决方案1

从已经索引以进行排序的现有列中选择。这可以包括创建时间、修改时间戳或任何其他您可能按排序方式排序的列。当用户首次访问站点时,请将它们方便地存储在数组中,随机选择一个,然后随机选择 ASCDESC

在您的情况下,每次用户返回到第一页时,请选择新内容并将其存储在会话中。每个后续页面,您都可以使用该排序来生成一致的分页集。

解决方案2

您可以有一个额外的列来存储排序用的随机数。显然,它应该被索引。定期运行以下查询:

UPDATE table SET rand_col = RAND();

由于您似乎要求每个用户在第一次访问页面1时看到不同的内容,因此这种方法可能不适用于您的规格要求。


2

首先,您应该停止使用ORDER BY RAND语法。在大量行的情况下,这会影响性能。

您需要手动确定LIMIT约束条件。如果您仍想使用随机结果,并且不希望用户在下一页看到相同的结果,则唯一的方法是将此搜索会话的所有结果保存在数据库中,并在用户导航到下一页时操作此信息。

在网站设计中,您需要了解的下一件事情是 - 在您的网站上使用任何随机数据块对用户的视觉感知非常非常不利。


2
这句话的意思是什么:“在网页设计中,任意随机的数据块对用户的视觉效果非常非常非常不好。” - Paul Dessert
这意味着网站上的所有可视块或数据必须符合特定的依赖关系(模型逻辑、时间、访问级别等),而不是随机的。想象一下,用户打开页面并看到一些随机块。“哇,酷块,很有趣,好的去喝茶..”。几分钟后他回来刷新页面。“我的天啊,我的酷块在哪里,它太有趣了”。 - odiszapc
3
可能是类似于随机推特讯息之类的东西。不能普遍地宣称随机性是不好的,如果这是有效的URL,点击返回按钮会保留预渲染的状态。 - FlavorScape
是的,像 Twitter 提供的信息(数以百万计的推文,非常大的数据集)可能会以随机数的方式显示,这是事实。但是搜索结果必须按照给定的特定非随机顺序显示。 - odiszapc

1

随机排序、分页和HTTP(无状态)的组合是最丑陋的:1和2需要某种“持久性随机”,而3使这更难实现。此外,1不是RDBMS优化的工作。

我的建议取决于您的数据集有多大:

少量行(约<1K):

  • 在第一个查询中选择所有PK值(第一页)
  • 在PHP中对这些进行洗牌
  • 将洗牌后的列表存储在会话中
  • 对于每个页面调用,根据存储的PK选择数据

许多行(10K+):

假设您有一个名为ID的AUTO_INCREMENT唯一键,并且具有可管理的空洞数量。如有需要(高删除比率),请使用维护脚本。

使用一个带参数的洗牌函数(例如会话ID)创建一个名为rand_id(continuous_id)的函数。 如果你需要例如记录100,000至100,009,则计算$a=array(rand_id(100,000), rand_id(100,001), ... rand_id(100,009)); $a=implode(',',$a); $sql="SELECT foo FROM bar WHERE ID IN($a) ORDER BY FIELD(ID,$a)"; 为了处理ID中的空洞,选择多几条记录(丢弃多余的),在选择的记录中循环。

0

你有几个问题需要处理!我建议你一步一步地解决。

第一个问题:已经看到的结果似乎不再出现

  1. 将每个返回的项目存储在一个数组中。(假设示例中的索引id)
  2. 当用户转到下一页时,将NOT IN传递给查询:

MySQL查询

SELECT * FROM table WHERE id NOT IN (1, 14, 25, 645) ORDER BY RAND() LIMIT 0,10;

这个操作是匹配所有不是1、14、25或645的id


就性能问题而言:以一种内存高效的方式
SELECT RAND( )
FROM table
WHERE id NOT
IN ( 1, 14, 25, 645 )
LIMIT 0 , 10

显示行0-9(共10行,查询耗时0.0004秒)

并且

SELECT *
FROM table
WHERE id NOT
IN ( 1, 14, 25, 645 )
ORDER BY RAND( )
LIMIT 0 , 10

显示第0行至第9行(共10行,查询耗时0.0609秒)

因此,请勿使用ORDER BY RAND(),最好使用SELECT RAND()。


SELECT RAND() 会生成像 0.308913599056111、0.521270950810305、0.679613987616835、0.834257116386906、0.132443649817671、0.159446930661282 等随机值。那么如何使用 SELECT RAND() 获取记录呢? - Yashrajsinh Jadeja
那么分页怎么办?如果使用IN,它将减少分页数量,并且第一页始终可访问。 - user3137766

0

我建议您让 PHP 生成随机的记录编号或行数并传递给查询,然后将其保存在用户客户端的 cookie 中,以指示他们已经看过哪些记录。

除非您要跟踪这些数据,否则没有理由将此用户特定数据存储于服务器上(反正这是随机的数据,谁会关心呢)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接