随机记录分页

 

问题(需求):

1. 每次打开第一页时,显示的记录不同。

2. 但是从第一页后的其他页面(第二页,第三页,。。。)返回第一页时,还是那个第一次打开第一页的那些记录

3. 每页记录不能重复

 

其实看起来很简单的一个问题,但是加上分页的要求之后,变得稍微有点复杂。

如果是随机从数据库取一条记录,最直接的方式,就是order by rand() limit 1:

当然,网上也有很多提高order by rand()效率的写法,都可以参考。

如果是不需要分页,那order by rand()及其优化方式,也都可以满足需求。

倘若需要分页,我们给rand指定一个参数,再配以limit,可以满足我们第2,3条要求。

因为如果每次请求rand的参数是不变的,那每次取的记录顺序也是一样的,这样每次每开第一页的记录,自然也是一样的。

重新连接数据库也一样。

 

pwwang@pwwang-xxx:~$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.5.8 Source distribution
 
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL v2 license
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql> use database;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
 
Database changed
mysql> select id from table order by rand(1);
+----+
| id |
+----+
|  1 |
|  3 |
|  8 |
|  4 |
|  7 |
|  6 |
|  9 |
|  5 |
+----+
8 rows in set (0.00 sec)
 
mysql> Bye
pwwang@pwwang-xxx:~$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.5.8 Source distribution
 
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL v2 license
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql> use database;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
 
Database changed
mysql> select id from table order by rand(1);
+----+
| id |
+----+
|  1 |
|  3 |
|  8 |
|  4 |
|  7 |
|  6 |
|  9 |
|  5 |
+----+
8 rows in set (0.00 sec)
 
mysql> Bye

 

重启数据库,结果还是一样

 

pwwang@pwwang-xxx:~$ sudo lampp restart
[sudo] password for pwwang:
Stopping XAMPP for Linux 1.7.4...
XAMPP: Stopping Apache with SSL...
XAMPP: Stopping MySQL...
XAMPP: Stopping ProFTPD...
XAMPP stopped.
Starting XAMPP for Linux 1.7.4...
XAMPP: Starting Apache with SSL (and PHP5)...
XAMPP: Starting MySQL...
XAMPP: Starting ProFTPD...
XAMPP for Linux started.
pwwang@pwwang-xxx:~$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.5.8 Source distribution
 
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL v2 license
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql> use database;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
 
Database changed
mysql> select id from table order by rand(1);
+----+
| id |
+----+
|  1 |
|  3 |
|  8 |
|  4 |
|  7 |
|  6 |
|  9 |
|  5 |
+----+
8 rows in set (0.00 sec)

 

因为给定参数之后,RAND的结果是一样的

 

mysql> select RAND(1);
+---------------------+
| RAND(1)             |
+---------------------+
| 0.40540353712197724 |
+---------------------+
1 row in set (0.02 sec)
 
mysql> select RAND(1);
+---------------------+
| RAND(1)             |
+---------------------+
| 0.40540353712197724 |
+---------------------+
1 row in set (0.00 sec)

 

如果我们每次在打开第一页的时候就给RAND一个随机的参数,那问题就可以解决了。

 

Solution 1:

 

$seed = $_GET['seed']; // 你也可以用session, cookie等方式来存储这个值
if( !isset($_GET['seed']) ){  
    $seed = rand();
}
$sql = "SELECT id FROM table ORDER BY RAND($seed) LIMIT $offset, $limit;";

 

页面渲染的时候每个和分页相关的链接都带上参数seed=$seed, 就可以保证在这个周期的记录浏览中,每一页的记录都是特定的。

在下个周期中,由于没有$_GET['seed']的值,$seed会重新随机取值,那么,第一页的记录也就和上个周期不一样了。

 

Solution 2:

当然,也有人说到了order by rand() 的效率问题。那我们可以用脚本事先把primary key(假设这里是increment的id)排好随机顺序,再传给sql去查询。

如果你的记录的id是连续的,你可以用max(id)来获取pk的序列:

 

$serial = explode(',', $_GET['serial']);
if( !$serial ){
    $maxid = $db->get_value("SELECT MAX(id) FROM table");
    $serial = range(1, $maxid);
    shuffle($serial);
}
$ids_array = array_slice($serial, $offset, $limit);
$ids = implode(',', $ids_array);
$sql = "SELECT id FROM table WHERE id IN ($ids);";

如果你单页的记录不多的话(一般也不会太多),这个替代方案可以很好的弥补order by rand的效率问题

 

但是如果你的记录id不是连续的,那么就想要办法先拿到id的序列:

 

$serial = $db->get_value("SELECT group_concat(id) FROM table");
$serial = explode(',', $serial);

这里只演示了如何获取id序列。其它机制和连续id是一样的。

 

不过这里用到了group_concat,来选取所有记录的id。

效率问题,大家可以根据实际情况在order by rand和group_concat之间权衡。

 

 

,
Trackback

3 comments untill now

  1. 变得稍微有点复杂

  2. 进来学习一下,收藏了。。

  3. 学习了,谢谢!

Add your comment now

;) :| :x :twisted: :roll: :oops: :o :mrgreen: :lol: :idea: :evil: :cry: :arrow: :P :D :?: :? :) :( :!: 8O 8)

你可以使用@somebody:开头, 来邮件通知somebody你回复了他的留言(用户名区分大小写).