【问题标题】:MySQL - inserting 70000 random unique strings efficientlyMySQL - 有效地插入 70000 个随机唯一字符串
【发布时间】:2017-06-02 18:47:22
【问题描述】:

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

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

【问题讨论】:

  • 使用唯一索引。
  • 在这种情况下,代码崩溃并且代码生成停止。
  • 如果您的代码因为(有效)约束失败而崩溃,那么也许可以修复您的代码。
  • 使用 INSERT IGNORE 静默丢弃代码崩溃,并使用唯一索引
  • 这将取决于 PDO 错误模式以及您如何处理它。如果你设置了异常,如果没有被捕获,它将停止脚本。

标签: php mysql random


【解决方案1】:

创建你的表结构:

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 来强制执行重复数据删除。

【讨论】:

  • 这是最聪明的做法。我在查询中添加了更多列,并在几秒钟内完成。太好了!
  • 这个答案很好!
【解决方案2】:

我会单独使用 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 毫秒内将 69934 个随机唯一字符串插入远程数据库(来自 70000 次运行)。以80000为参数运行同样的程序

call createRandomString(80000);

为我运行 12 秒 434 毫秒,插入 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;

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      这里有一个想法......

      我在这里插入(大约)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       |
      +-----------+
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-13
        • 2016-02-07
        • 2015-10-22
        • 1970-01-01
        • 2021-12-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多