生成一百万个唯一的随机12位数字

时间:2016-11-01 09:54:01

标签: php mysql optimization random unique

我需要为刮刮卡应用程序生成近百万(100批10000个数字)唯一且随机的12位数代码。此过程将重复进行,并且每次都需要生成相同数量的代码。

此外,生成的代码需要在数据库中输入,以便以后当消费者在我的网站上输入时可以对其进行验证。我正在使用PHP和Mysql来做到这一点。这些是我正在遵循的步骤

  1. 获取有关批次数和每批代码的管理员输入

  2. 使用for循环生成代码 mt_rand(100000000000,999999999999)

  3. 每次生成一个数字时检查是否存在重复项 在数据库中,如果没有添加到结果变量,则重新生成。

  4. 如果唯一

  5. ,则在db中保存生成的数字
  6. 在所需的代码数量上重复b,c和d

  7. 在csv中输出代码到admin

  8. 使用的代码(删除了大部分注释,使其不那么详细,因为我之前已经解释了这些步骤):

    $totalLabels = $numBatch*$numLabelsPerBatch;
    // file name for download
    $fileName = $customerName."_scratchcodes_" . date('Ymdhs') . ".csv";
    $flag = false;
    $generatedCodeInfo = array();
    // headers for download
    header("Content-Disposition: attachment; filename=\"$fileName\"");
    header("Content-Type: application/vnd.ms-excel");
    $codeObject = new Codes();
    //get new batch number 
    $batchNumber = $codeObject->getLastBatchNumber() + 1;
    $random = array();
    for ($i = 0; $i < $totalLabels; $i++) {
        do{
            $random[$i] = mt_rand(100000000000,999999999999); //need to optimize this to reduce collisions given the databse will be grow
        }while(isCodeNotUnique($random[$i],$db));
        $codeObject = new Codes();
        $codeObject->UID = $random[$i];
        $codeObject->customerName = $customerName;
        $codeObject->batchNumber = $batchNumber;
        $generatedCodeInfo[$i] = $codeObject->addCode();
    
        //change batch number for next batch
        if($i == ($numLabelsPerBatch-1)){$batchNumber++;}
    
    
        //$generatedCodeInfo[i] = array("UID" => 10001,"OID"=>$random[$i]);
        if(!$flag) {
            // display column names as first row
            echo implode("\t", array_keys($generatedCodeInfo[$i])) . "\n";
            $flag = true;
        }
        // filter data
        array_walk($generatedCodeInfo[$i], 'filterData');
        echo implode("\t", array_values($generatedCodeInfo[$i])) . "\n";
    
    
    }
    
    
    function filterData(&$str)
    {
        $str = preg_replace("/\t/", "\\t", $str);
        $str = preg_replace("/\r?\n/", "\\n", $str);
        if(strstr($str, '"')) $str = '"' . str_replace('"', '""', $str) . '"';
    }
    
    function isCodeNotUnique($random){
        $codeObject = new Codes();
        $codeObject->UID = $random;
        if(!empty($codeObject->getCodeByUID())){
            return true;
        }
        return false;
    }
    

    现在这需要很长时间才能执行,我认为这不是最佳选择。

    1. 如何进行优化以便快速生成唯一的随机数?

    2. 如果用mysql或其他方式而不是php生成数字会更快吗?如果是这样的话我该如何做?

    3. 当db开始增长时,步骤b中的重复检查将非常耗时,所以我该如何避免?

    4. mysql中的行数是否有限制?

    5. 注意:在应用程序的整个生命周期内,所有批次中的数字必须是唯一的。

5 个答案:

答案 0 :(得分:1)

1)根据批次数将您的数字范围划分为更小的范围。例如。如果您的范围为0 - 1000且您有10批次,则批次为0 - 99,下一批为100 - 199,等等。生成批次编号时,只生成批次范围内的随机数。这样您就知道批次中只能有重复的数字。

单独将每个数字插入数据库,但将它们存储在数组中。生成新的随机数时,请使用in_array()函数检查数组,而不是数据库。批处理完成后,使用单个insert语句插入批处理的内容:

insert into yourtable (bignumber) values (1), (2), ..., (n)

检查MySQL的max_allowed_packet设置,看看它是否能够一次性接收完整的sql语句。

实施回退计划,以防万一在插入过程中仍然找到重复值(错误处理和数字重新生成)。

2)MySQL在程序性方面不是那么好,所以我会坚持使用外部语言,比如php。

3)在包含随机数的字段上添加唯一索引。如果您尝试插入重复记录,MySQL将阻止它并引发错误。这真的很快。

4)根据所使用的实际表引擎(innodb,myisam等),其配置和操作系统,某些限制可能适用于表的大小。有关更详细的答案,请参阅此处的Maximum number of records in a MySQL database table问题(查看最受欢迎的答案,而不是已接受的答案)。

答案 1 :(得分:1)

您可以执行以下操作:

$random = getExistingCodes(); // Get what you already have (from the DB).  
$random = array_flip($random); //Make them into keys
$existingCount = count($random); //The codes you already have 

do {
    $random[mt_rand(100000000000,999999999999)] = 1;
} while ((count($random)-$existingCount) < $totalLabels);

$random = array_keys($random);

当您生成重复的数字时,它只会覆盖该密钥而不会增加计数。

要插入,您可以启动事务并根据需要执行尽可能多的插入操作。 MySQL将尝试优化单个事务中的所有操作。

答案 2 :(得分:0)

这是一个生成100万个伪随机数而无需重复的查询:

select cast(  (@n := (13*@n + 97) % 899999999981)+1e11 as char(12)) as num
from   (select @n := floor(rand() * 9e11) ) init,
       (select 1 union select 2) m01,
       (select 1 union select 2) m02,
       (select 1 union select 2) m03,
       (select 1 union select 2) m04,
       (select 1 union select 2) m05,
       (select 1 union select 2) m06,
       (select 1 union select 2) m07,
       (select 1 union select 2) m08,
       (select 1 union select 2) m09,
       (select 1 union select 2) m10,
       (select 1 union select 2) m11,
       (select 1 union select 2) m12,
       (select 1 union select 2) m13,
       (select 1 union select 2) m14,
       (select 1 union select 2) m15,
       (select 1 union select 2) m16,
       (select 1 union select 2) m17,
       (select 1 union select 2) m18,
       (select 1 union select 2) m19,
       (select 1 union select 2) m20
limit 1000000;

如何运作

首先生成随机整数值 n 0&lt; = n&lt; 9000亿。该数字将具有生成序列的种子功能:

@n := floor(rand() * 9e11)

通过内联记录对的多个(20)连接,此单个记录乘以2 20 副本,这只是略多于100万。

然后选择开始,并且在获取记录后的记录时, @n 变量的值将根据此增量公式进行修改:

@n := (13*@n + 97) % 899999999981

此公式为linear congruential generator。这三个常数需要遵守一些规则以最大化周期(非重复),但是当899999999981是素数时它是最容易的,它就是。在这种情况下,我们有一个899999999981的周期,这意味着第一个899999999981生成的数字将是唯一的(我们需要更少)。这个数字实际上是900亿以下的最大素数。

作为最后一步,将100000000000添加到号码中以确保号码总是有12位数,因此不包括小于100000000000的号码。由于899999999981的选择,将有20个号码永远不会生成,即介于999999999981和999999999999之间的那些。

由于这会生成2个 20 记录,limit子句将确保将其删除到恰好一百万条记录。

castchar(12)是可选的,但可能需要将12位数字可视化,而不用科学记数法在屏幕上呈现这些数字。如果您将使用它来插入记录,并且目标数据类型是数字,那么您当然会忽略此转换。

答案 3 :(得分:0)

CREATE TABLE x (v BIGINT(12) ZEROFILL NOT NULL PRIMARY KEY);

INSERT IGNORE INTO x (v) VALUES
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()),
    (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND()), (FLOOR(1e12*RAND());

执行INSERT 1e6 / 15次。

检查COUNT(*),看看你是否有一百万。这样做直到表格为百万行:

INSERT IGNORE INTO x (v) VALUES
    (FLOOR(1e12*RAND());

注意:

  • ZEROFILL假设您希望显示屏有前导零。
  • IGNORE是因为会有一些重复项。这避免了每次插入后的昂贵检查。
  • &#34;批量插入&#34;一次比一行快。 (一次做100个是最佳的,但我很懒。)
  • 潜在问题:虽然我认为RAND()的值模式不会重复,例如2 ^ 16或2 ^ 32值,但我不知道事实。如果你不能达到一百万,那么随机数发生器是坏的;你应该切换到PHP的rand或其他东西。
  • 注意线性后续随机数生成器。他们可能很容易被黑客入侵。 (我假设有一些&#34;钱&#34;在刮刮卡后面。)

答案 4 :(得分:0)

不要计划 left_join() 在小范围内是唯一的

mt_rand()

示例输出:

<?php
// Does mt_rand() repeat?

TryMT(100);
TryMT(100);
TryMT(1000);
TryMT(10000);
TryMT(1e6);
TryMT(1e8);
TryMT(1e10);
TryMT(1e12);
TryMT(1e14);

function TryMT($max) {
    $h = [];
    for ($j = 0; $j<$max; $j++) {
        $v = mt_rand(1, $max);
        if (isset($h[$v])) {
            echo "Dup after $j iterations (limit=$max)<br>\n";

            return;
        }
        $h[$v] = 1;
    }
}

Dup after 7 iterations (limit=100)<br> Dup after 13 iterations (limit=100)<br> Dup after 29 iterations (limit=1000)<br> Dup after 253 iterations (limit=10000)<br> Dup after 245 iterations (limit=1000000)<br> Dup after 3407 iterations (limit=100000000)<br> Dup after 29667 iterations (limit=10000000000)<br> Dup after 82046 iterations (limit=1000000000000)<br> Dup after 42603 iterations (limit=1.0E+14)<br> 是一个“好的”随机数,因为它确实有重复。