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


Generate a million unique random 12 digit numbers

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

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

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

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

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

  4. 如果唯一,则将生成的数字保存在数据库中

  5. 在所需数量的代码上重复b、c和d

  6. 以csv 向管理员输出代码

使用的代码(删除了大部分注释,使其不那么冗长,因为我已经在前面解释了步骤(:

$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. 当数据库开始增长时,步骤b中的重复检查将非常耗时,那么我该如何避免这种情况呢?

  4. mysql中的行数有限制吗?

注意:在应用程序的整个生命周期内,所有批次的编号都必须是唯一的。

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等(、其配置和操作系统,表的大小可能会受到某些限制。请参阅SO上的MySQL数据库表中的最大记录数问题,以获得更详细的答案(查看投票最多的答案,而不是接受的答案(。

您可以执行以下操作:

$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将尝试在单个事务中优化所有操作。

以下是一个生成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<=n<9000000000。这个数字将具有生成序列的种子的功能:

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

通过具有内联记录对的多(20(个连接,将单个记录相乘为220个拷贝,这略高于100万。

然后开始选择,当获取一条又一条记录时,@n变量的值将根据以下增量公式进行修改:

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

这个公式是一个线性同余生成器。三个常数需要遵守一些规则来最大限度地延长周期(不重复(,但当89999999981是素数时,这是最容易的。在这种情况下,我们有一个899999 99981的周期,这意味着生成的第一个89999 99981的数将是唯一的(我们需要的要少得多(。这个数字实际上是9000000000以下的最大素数。

作为最后一步,将100000000000添加到数字中,以确保数字始终有12位数字,因此排除小于100000000000的数字。由于选择了89999999981,将有20个数字永远不会生成,即介于9999999999981和999999999999之间的数字。

由于这会生成220记录,limit子句将确保将其截断为正好一百万条记录。

castchar(12)是可选的,但可能有必要在不以科学符号显示在屏幕上的情况下将12位数字可视化。如果您将使用它来插入记录,并且目标数据类型是数字,那么您当然会忽略此转换。

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());

插入1e6/15次。

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

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

注:

  • ZEROFILL假设您希望显示器具有前导零
  • IGNORE是因为会有一定数量的重复。这样可以避免每次插入后进行昂贵的检查
  • "批量插入"比一次插入一行要快。(一次做100是最佳的,但我很懒。(
  • 潜在问题:虽然我认为RAND()的值模式不会重复,比如2^16或2^32的值,但我不知道事实。如果你不能达到一百万,那么随机数生成器就不好了;您应该切换到PHP的rand或其他版本
  • 小心线性间接随机数生成器。它们可能很容易被黑客入侵。(我认为刮刮卡背后有一些"钱"。(

不要计划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>

CCD_ 12是";"好";由于确实有重复而生成的随机数。