繁体   English   中英

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

[英]MySQL - inserting 70000 random unique strings efficiently

我正在做一个项目,在该项目中我应该生成至少 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;
}

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

然后我决定使用数据库端方法并使用此解决方案: 使用 MySQL 生成随机且唯一的 8 个字符串

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

你能提出一个更好的解决方案吗?

创建你的表结构:

CREATE TABLE t (code CHAR(8) 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): void {
    $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来强制执行重复数据删除。

我会单独使用 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);

我做了一个快速测试,我在 10 秒 603 毫秒内在远程数据库(来自 70000 次运行)上插入了 69934 个随机的唯一字符串。 以 80000 作为参数运行相同的程序

call createRandomString(80000);

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

会产生这样的结果:

在此处输入图片说明


如果要确保准确插入所调用的行数,请使用此命令(但请注意,在调用过程后将 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;

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

我在这里插入(大约)16 个唯一的 3 个字符 (0-9/az) 字符串...

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       |
+-----------+

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM