[英]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
这段代码中的相关技巧是:
INSERT INTO ... VALUES (), (), ... ();
- 这真的很有帮助,因为它最大限度地减少了 MySQL 必须执行的语句处理总量,并且它告诉我们插入了多少条记录而无需执行另一个查询。INSERT IGNORE
可以避免检查我们插入的每个代码是否存在,这真的非常昂贵。mcrypt_create_iv
是一个快速生成器,具有加密安全性,因此它提供了安全性和性能的理想平衡。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.