如何回应数据库中的随机行?

时间:2015-12-01 03:24:53

标签: php mysql

我有一个大约有1.6亿行的数据库表。

该表格有两列:idlisting

我只需要使用PHP在listing列中显示1000个随机行,并将它们放入<span>标记中。像这样:

<span>Row 1</span>
<span>Row 2</span>
<span>Row 3</span>

我一直在尝试用ORDER BY RAND()来做这件事但是在这么大的数据库上加载需要很长时间,而我却找不到任何其他解决方案。

我希望有一种快速/简单的方法来做到这一点。我无法想象简单回显1000个随机行是不可能的......谢谢!

5 个答案:

答案 0 :(得分:2)

这里介绍了两个解决方案。这两个提出的解决方案都只是mysql,可以被任何编程语言用作消费者。 PHP可能会非常慢,但它可能是它的消费者。

更快的解决方案:我可以使用更高级的编程技术,在大约十分之二秒的时间内从一个包含1900万行的表中提取1000个随机行。

更慢的解决方案:使用非电源编程技术大约需要15秒。

顺便说一句,两者都使用我写的HERE数据生成。这就是我的小架构。我使用它,继续在那里看到 TWO 更多自我插入,直到我有19M行。所以我不打算再说一遍。但要获得这些19M行,请查看,并再执行2次插入,并且您有19M行。

首先是较慢的版本

首先,方法较慢。

select id,thing from ratings order by rand() limit 1000;

在15秒内返回1000行。

  

对于刚接触mysql的人,请不要阅读以下内容。

更快的解决方案

这个描述要复杂一点。它的要点是,您预先计算随机数并生成随机数的in clause 结尾,用逗号分隔,并用一对括号括起来。

它看起来像(1,2,3,4),但它中会包含1000个数字。

然后存储它们,并使用它们一次。就像密码学的一次性填充。好吧,不是一个很好的类比,但你明白了我的意思。

将其视为in子句的结尾,并存储在TEXT列中(如blob)。

为什么世界会想要这样做?因为 RNG (随机数生成器)非常慢。但是用几台机器生成它们可能能够相对快速地生成数千台机器。顺便说一下(你会在我所谓的附录的结构中看到这个,我捕获生成一行需要多长时间。使用mysql大约1秒。但是C#,PHP,Java,任何东西都可以把它放在一起。不是你把它放在一起的方式,而是你想要它的时候。

这个策略的长短不一,当它与一个尚未用作随机列表的行相关联时,将其标记为已使用,并发出一个调用,如

select id,thing from ratings where id in (a,b,c,d,e, ... )

并且in子句中有1000个数字,结果在不到半秒的时间内可用有效地使用mysql CBO(基于成本的优化器)比将其视为PK上的连接索引。

我将其以摘要形式保留,因为它在实践中有点复杂,但可能包括以下粒子

  • 包含预先计算的随机数的表(附录A)
  • 一个mysql创建事件策略(附录B)
  • 使用准备好的声明(附录C)
  • 的存储过程
  • 一个仅限mysql的存储过程来演示用于踢的RNG in子句(附录D)

附录A

包含预先计算的随机数的表

create table randomsToUse
(   -- create a table of 1000 random numbers to use
    -- format will be like a long "(a,b,c,d,e, ...)" string

    -- pre-computed random numbers, fetched upon needed for use

    id int auto_increment primary key,
    used int not null,  -- 0 = not used yet, 1= used
    dtStartCreate datetime not null, -- next two lines to eyeball time spent generating this row
    dtEndCreate datetime not null,
    dtUsed datetime null, -- when was it used
    txtInString text not null -- here is your in clause ending like (a,b,c,d,e, ... )
    -- this may only have about 5000 rows and garbage cleaned
    -- so maybe choose one or two more indexes, such as composites
);

附录B

为了不把它变成一本书,请参阅我的答案HERE,了解运行重复mysql事件的机制。它将使用附录D中的技术和您想要想到的其他想法推动附录A中所见的表的维护。如重复使用行,存档,删除等等。

附录C

存储过程简单地给我1000个随机行。

DROP PROCEDURE if exists showARandomChunk;
DELIMITER $$
CREATE PROCEDURE showARandomChunk
(
)
BEGIN
  DECLARE i int;
  DECLARE txtInClause text;

  -- select now() into dtBegin;

  select id,txtInString into i,txtInClause from randomsToUse where used=0 order by id limit 1;
  -- select txtInClause as sOut; -- used for debugging

  -- if I run this following statement, it is 19.9 seconds on my Dell laptop
  -- with 19M rows
  -- select * from ratings order by rand() limit 1000; -- 19 seconds

  -- however, if I run the following "Prepared Statement", if takes 2 tenths of a second
  -- for 1000 rows

  set @s1=concat("select * from ratings where id in ",txtInClause);

  PREPARE stmt1 FROM @s1;
  EXECUTE stmt1; -- execute the puppy and give me 1000 rows
  DEALLOCATE PREPARE stmt1;
END
$$
DELIMITER ;

附录D

可以与附录B概念交织在一起。但是你想要这样做。但它让你有一些东西可以看看mysql如何在RNG方面自行完成。顺便说一下,对于参数1和2分别为1000和19M,我的机器需要800毫秒。

这个例程可以用开头提到的任何语言编写。

drop procedure if exists createARandomInString;
DELIMITER $$
create procedure createARandomInString
(   nHowMany int, -- how many numbers to you want
    nMaxNum int -- max of any one number
)
BEGIN
    DECLARE dtBegin datetime;
    DECLARE dtEnd datetime;
    DECLARE i int;
    DECLARE txtInClause text;
    select now() into dtBegin;

    set i=1;
    set txtInClause="(";
    WHILE i<nHowMany DO
        set txtInClause=concat(txtInClause,floor(rand()*nMaxNum)+1,", "); -- extra space good due to viewing in text editor
        set i=i+1;
    END WHILE;
    set txtInClause=concat(txtInClause,floor(rand()*nMaxNum)+1,")");
    -- select txtInClause as myOutput; -- used for debugging
    select now() into dtEnd;

    -- insert a row, that has not been used yet
    insert randomsToUse(used,dtStartCreate,dtEndCreate,dtUsed,txtInString) values 
       (0,dtBegin,dtEnd,null,txtInClause);
END
$$
DELIMITER ;

如何调用上面存储的proc:

call createARandomInString(1000,18000000);

生成并保存1行,如上所述包裹1000个数字。大数字,1到18M

作为一个快速说明,如果要修改存储过程,请取消底部附近的行,用于调试&#34;并将其作为最后一行,在存储过程中运行,然后运行:

call createARandomInString(4,18000000);

...生成4个随机数,最多18M,结果可能看起来像

+-------------------------------------+
| myOutput                            |
+-------------------------------------+
| (2857561,5076608,16810360,14821977) |
+-------------------------------------+

附录E

现实检查。这些是一些先进的技术,我不能指导任何人。但无论如何我想分享它们。但我不能教它。过来。

答案 1 :(得分:1)

你想在php中使用rand函数。签名是

rand(min, max);

所以,将表格中的行数设为$ var并将其设置为max。 使用SQL执行此操作的方法是

SELECT COUNT(*) FROM table_name;

然后简单地运行一个循环以使用上述函数生成1000个rand并使用它们来获取特定的行。

如果ID不是连续的,但如果它们是关闭的,您只需测试每个rand ID以查看是否有命中。如果它们相距很远,您可以将整个ID空间拉入php,然后通过类似

的方式从该分发中随机抽样
$random = rand(0, count($rows)-1);

表示$rows中的ID数组。

答案 2 :(得分:1)

如果您的RAND()函数太慢,并且您只需要准随机记录(对于测试样本)而不是真正随机的记录,您可以通过按中间字符排序来创建一个快速,有效随机的组(在索引字段中使用SUBSTRING)。例如,按电话号码的第7位排序......按降序排序......然后按第6位排序......按升序排序......这已经是准随机的。您可以对字符列执行相同操作:人名中的第6个字符将无意义/随机等。

答案 3 :(得分:1)

ORDER BY RAND()是一个mysql函数,可以很好地处理小型数据库,但是如果运行大于10k行的任何东西,你应该在程序中构建函数,而不是使用mysql预制函数或以特殊方式组织数据。 / p>

我的建议:保持你的mysql数据被自动增量id索引,或者添加其他增量和唯一行。

然后构建一个select函数:

<?php
//get total number of rows
$result = mysql_query('SELECT `id` FROM `table_name`', $link); 
$num_rows = mysql_num_rows($result); 

$randomlySelected = [];

for( $a = 0; $a < 1000; $a ++ ){

        $randomlySelected[$a] = rand(1,$num_rows);

}

//then select data by random ids
$where = "";

$control = 0;
foreach($randomlySelected as $key => $selectedID){

    if($control == 0){

        $where .= "`id` = '". $selectedID ."' ";

    } else {

        $where .= "OR `id` = '". $selectedID ."'";

    }
    $control ++;
}


$final_query = "SELECT * FROM `table_name` WHERE ". $where .";";
$final_results = mysql_query($final_query);    

?>

如果缺少160万个数据库中的某些增量ID,则可以轻松添加一个函数来添加另一个随机ID(可能是一个while循环),如果随机选择的ID数组少于所需数量。 / p>

如果您需要进一步的帮助,请告诉我。

答案 4 :(得分:0)

请在select语句中使用mysql rand。您的查询将如下所示

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