MySQL - 有效地插入70000个随机唯一字符串

时间:2017-01-18 11:45:21

标签: php mysql random

我正在处理一个项目,我应该生成至少70000个包含8个字母数字字符的代码。代码必须是唯一的。目前我正在使用php通过以下函数生成这些代码:

function random_unique_serial($length, PDO $conn) {
    $codeCheck=FALSE;
    while (!$codeCheck) {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
        $charactersLength = strlen($characters);
        $randomCode = '';
        for ($i = 0; $i < $length; $i++) {
            $randomCode .= $characters[rand(0, $charactersLength - 1)];
        }
        $sql = "SELECT * FROM codes WHERE code=:code";
        $st = $conn->prepare($sql);
        $st->bindvalue(":code", $randomCode, PDO::PARAM_STR);
        $st->execute();
        $count = $st->rowcount();
        if ($count==0) {
            $codeCheck=TRUE;
        } else {
            $codeCheck=FALSE;
        }
    }
    return $randomCode;
}

如您所见,此代码会检查数据库中是否生成了每个代码,以确保它不是重复的。这应该在理论上有效。但是这非常慢并导致请求超时。我尝试增加执行时间,但这也没有帮助。

然后我决定使用数据库端方法并使用此解决方案: Generating a random & unique 8 character string using MySQL

这也很慢,一些生成的代码长度不到8个字符。

你能建议一个更好的解决方案吗?

4 个答案:

答案 0 :(得分:3)

创建表结构:

CREATE TABLE t (code CHAR(8) UNIQUE CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL UNIQUE);

定义PHP函数以生成随机字符串:

function random_string(integer $length = 8): string {
    return bin2hex(mcrypt_create_iv(ceil($length/2), MCRYPT_DEV_URANDOM));
}

使用PHP构建一个多值INSERT语句,将其压入数据库,计算插入的数量,然后重复直到插入所需的数字:

function insert_records(\PDO $pdo, integer $need = 70000): null {
    $have = 0;
    while ($have < $need) {
        // generate multi value INSERT
        $sql = 'INSERT IGNORE INTO t VALUES ';
        for ($i = 1; $i < $need; $i++) {
            $sql .= sprintf('("%s"),', random_string());
        }
        $sql .= sprintf('("%s");', random_string());

        // pass to database and ask how many records were inserted
        $result = $pdo->query($sql);
        $count  = $result->rowCount();

        // adjust bookkeeping values so we know how many we have and how many
        // we need
        $need -= $count;
        $have += $count;
    }
}

在我的机器上(Amazon Linux c2.small),70k记录的运行时间约为2秒:

real    0m2.136s
user    0m1.256s
sys     0m0.212s

为了加快速度,此代码中的相关技巧是:

  • 发送生成所需记录数所需的最少SQL语句数。使用多值插入 - INSERT INTO ... VALUES (), (), ... (); - 真正有助于此,因为它最大限度地减少了MySQL必须执行的语句处理总量它告诉我们插入了多少条记录而无需另外执行查询。
  • 使用INSERT IGNORE来避免检查我们插入的每个代码是否存在,这真的非常昂贵。
  • 使用最快的字符串生成功能,我们可以满足我们的需求。根据我的经验,mcrypt_create_iv是一个加密安全的快速生成器,因此它提供了安全性和性能的理想平衡。
  • 使用ASCII字符集和固定宽度CHAR删除不必要的字节开销,UNIQUE强制执行重复数据删除。

答案 1 :(得分:1)

我单独使用mysql,存储过程会有所帮助 - 您仍然可以使用php创建和调用它。存储过程使用从rand()创建的md5哈希的子字符串。要插入字符串的列需要为unique。替换此部分中的表名和列:

insert ignore into foo (`uniqueString`)
delimiter //
create procedure createRandomString (in num int)
  begin
    declare i int default 0;
    while i < num do
      insert ignore into foo (`uniqueString`) values (substr(md5(rand()), 1, 8));
      set i = i + 1;
    end while;
  end //
delimiter ;

call createRandomString (70000);

我做了一个快速测试,我在10s 603ms内在远程数据库(从70000次运行)中插入了69934个随机唯一字符串。使用80000作为参数运行相同的过程

call createRandomString(80000);

为我运行12s 434ms,插入77354行 - 所以你在很短的时间内至少有70000个。

会产生这样的结果:

this one

如果你想确保完全按照被调用的方式插入行数,请使用它(但请注意将max_sp_recursion_depth设置为调用过程之前的内容,默认为0):

delimiter //
create procedure createRandomString2 (in num int)
  begin
    declare i int default 0;
    while i < num do
      insert ignore into foo (uniqueString) values (substr(md5(rand()), 1, 8));
      set i = i + 1;
    end while;
    if (select count(id) from foo) < num then
      call createRandomString2(num - (select count(id) from foo));
   END IF;
  end //
delimiter ;

set max_sp_recursion_depth = 100; 
call createRandomString7 (70000);
set max_sp_recursion_depth = 0;

答案 2 :(得分:0)

这是一个想法......

这里我插入(约)16个独特的3字符(0-9 / a-z)字符串......

DROP TABLE IF EXISTS my_table;

CREATE TABLE my_table (my_string CHAR(3) NOT NULL PRIMARY KEY);

INSERT INTO my_table 
SELECT CONCAT(SUBSTR('0123456789abcdefghihjlmnopqrstuvwxyz',(RAND()*35)+1,1)
             ,SUBSTR('0123456789abcdefghihjlmnopqrstuvwxyz',(RAND()*35)+1,1)
             ,SUBSTR('0123456789abcdefghihjlmnopqrstuvwxyz',(RAND()*35)+1,1)
             ) x;


//Repeat this block as necessary

INSERT IGNORE INTO my_table 
SELECT CONCAT(SUBSTR('0123456789abcdefghihjlmnopqrstuvwxyz',(RAND()*35)+1,1)
             ,SUBSTR('0123456789abcdefghihjlmnopqrstuvwxyz',(RAND()*35)+1,1)
             ,SUBSTR('0123456789abcdefghihjlmnopqrstuvwxyz',(RAND()*35)+1,1)
             ) x
          FROM my_table;

//End of block


SELECT * FROM my_table;
+-----------+
| my_string |
+-----------+
| 0he       |
| 112       |
| 24c       |
| 322       |
| 4b7       |
| 7vq       |
| as7       |
| g7n       |
| h66       |
| i54       |
| idd       |
| m62       |
| mqt       |
| obh       |
| x75       |
| xz4       |
+-----------+

答案 3 :(得分:0)

保证唯一的八位数字:00000000,00000001,00000002,......如果您不希望代码如此明显,则选择八组不同的十个字母数字字符来替换给定位置的十位数字。仍然会有一种模式,但不太明显:ql4id78sk,ql4id78s3,ql4id78sa,......

除此之外,您还可以加密原始数字,并确保加密是唯一的。 32位块密码将产生四个字节的结果,产生八个十六进制字符。