使用MySQL生成一个随机唯一的8个字符的string

我正在做一个涉及车辆的游戏。 我有一个名为“车辆”的MySQL表,其中包含有关车辆的数据,包括存储车辆牌照的栏“板”。

现在来看看我遇到的问题。 我需要在创build新车之前find一个未使用的车牌 – 它应该是一个字母数字的8个字符的随机string。 我是如何实现这个function的,是在Lua中使用一个while循环来生成string,并查询数据库是否被使用。 但是,随着车辆数量的增加,我预计这会变得更加低效。 因此,我决定尝试使用MySQL查询来解决这个问题。

我需要的查询应该简单地生成一个8个字符的字母数字string,它不在表格中。 我想到了再次生成和检查循环的方法,但我并没有限制这个问题,以防万一有一个更有效的问题。 我已经能够通过定义一个包含所有允许的字符和随机子串的string来生成string。

任何帮助表示赞赏。

这个问题包含两个非常不同的子问题:

  • 该string必须是表面上随机的
  • 该string必须是唯一的

虽然随机性很容易实现,但没有重试循环的唯一性不是。 这使我们首先专注于独特性。 使用AUTO_INCREMENT可以简单地实现非随机唯一性。 所以使用保持唯一性的伪随机变换将是很好的:

  • 哈希已被@paulbuild议
  • AESencryption也适合
  • 但有一个很好的: RAND(N)本身!

保证是由相同的种子创build的一个随机数序列

  • 重复性
  • 不同的前8次迭代
  • 如果种子是INT32

所以我们使用@ AndreyVolk或@ GordonLinoff的方法,但使用种子 RAND

例如Assumin id是一个AUTO_INCREMENT列:

 INSERT INTO vehicles VALUES (blah); -- leaving out the number plate SELECT @lid:=LAST_INSERT_ID(); UPDATE vehicles SET numberplate=concat( substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@lid)*4294967296))*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed:=round(rand(@seed)*4294967296))*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand(@seed)*36+1, 1) ) WHERE id=@lid; 

如何计算连续整数的MD5(或其他)散列,然后取前8个字符。

 MD5(1) = c4ca4238a0b923820dcc509a6f75849b => c4ca4238 MD5(2) = c81e728d9d4c2f636f067f89cc14862c => c81e728d MD5(3) = eccbc87e4b5ce2fe28308fd9f2a7baf3 => eccbc87e 

等等

警告:我不知道你有多less可以在碰撞前分配(但它将是一个已知和恒定的价值)。

编辑:这现在是一个旧的答案,但我在手上再次看到它,所以,从观察…

所有数字的机会= 2.35%

所有字母的机会= 0.05%

MD5(82945)=“7b763dcb …”(与MD5(25302)相同的结果)的第一次碰撞

正如我在评论中所说,我不会为碰撞的可能性而烦恼。 只要生成一个随机的string,并检查它是否存在。 如果是这样,再试一次,除非已经分配了大量的印版,否则你不需要再做几次。

在纯(My)SQL中生成一个8字符长的伪随机string的另一个解决scheme:

 SELECT LEFT(UUID(), 8); 

您可以尝试以下(伪代码):

 DO SELECT LEFT(UUID(), 8) INTO @plate; INSERT INTO plates (@plate); WHILE there_is_a_unique_constraint_violation -- @plate is your newly assigned plate number 

以下是一种使用字母数字作为有效字符的方法:

 select concat(substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1), substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', rand()*36+1, 1) ) as LicensePlaceNumber; 

请注意,不能保证唯一性。 你必须单独检查。

你可以使用MySQL的rand()和char()函数:

 select concat( char(round(rand()*25)+97), char(round(rand()*25)+97), char(round(rand()*25)+97), char(round(rand()*25)+97), char(round(rand()*25)+97), char(round(rand()*25)+97), char(round(rand()*25)+97), char(round(rand()*25)+97) ) as name; 

创build一个随机string

这里有一个MySQL函数来创build一个给定长度的随机string。

 DELIMITER $$ CREATE DEFINER=`root`@`%` FUNCTION `RandString`(length SMALLINT(3)) RETURNS varchar(100) CHARSET utf8 begin SET @returnStr = ''; SET @allowedChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; SET @i = 0; WHILE (@i < length) DO SET @returnStr = CONCAT(@returnStr, substring(@allowedChars, FLOOR(RAND() * LENGTH(@allowedChars) + 1), 1)); SET @i = @i + 1; END WHILE; RETURN @returnStr; END 

用法SELECT RANDSTRING(8)返回一个8个字符的string。

您可以自定义@allowedChars

唯一性是不能保证的 – 正如你将在其他解决scheme的评论中看到的,这是不可能的。 相反,您需要生成一个string,检查它是否已被使用,如果是,请重试。


检查随机string是否已被使用

如果我们想让碰撞检查代码不在应用程序中,我们可以创build一个触发器:

 DELIMITER $$ CREATE TRIGGER Vehicle_beforeInsert BEFORE INSERT ON `Vehicle` FOR EACH ROW BEGIN SET @vehicleId = 1; WHILE (@vehicleId IS NOT NULL) DO SET NEW.plate = RANDSTRING(8); SET @vehicleId = (SELECT id FROM `Vehicle` WHERE `plate` = NEW.plate); END WHILE; END;$$ DELIMITER ; 

您可以生成一个随机的字母数字string:

 lpad(conv(floor(rand()*pow(36,8)), 10, 36), 8, 0); 

您可以在BEFORE INSERT触发器中使用它,并在while循环中检查重复:

 CREATE TABLE `vehicles` ( `plate` CHAR(8) NULL DEFAULT NULL, `data` VARCHAR(50) NOT NULL, UNIQUE INDEX `plate` (`plate`) ); DELIMITER // CREATE TRIGGER `vehicles_before_insert` BEFORE INSERT ON `vehicles` FOR EACH ROW BEGIN declare str_len int default 8; declare ready int default 0; declare rnd_str text; while not ready do set rnd_str := lpad(conv(floor(rand()*pow(36,str_len)), 10, 36), str_len, 0); if not exists (select * from vehicles where plate = rnd_str) then set new.plate = rnd_str; set ready := 1; end if; end while; END// DELIMITER ; 

现在就插入你的数据

 insert into vehicles(col1, col2) values ('value1', 'value2'); 

触发器将为plate生成一个值。

( sqlfiddle演示 )

如果列允许NULL,那么这就是这样工作的。 如果你想要它不是NULL,你需要定义一个默认值

 `plate` CHAR(8) NOT NULL DEFAULT 'default', 

你也可以在触发器中使用任何其他的随机string生成algorithm,如果大写的字母数字不是你想要的。 但触发器会照顾到唯一性。

我使用另一列的数据来生成一个“哈希”或唯一的string

 UPDATE table_name SET column_name = Right( MD5(another_column_with_data), 8 ) 

如果您可以使用“随机”但完全可预测的车牌,您可以使用线性反馈移位寄存器来select下一个车牌号码 – 在重复之前保证通过每个号码。 但是,如果没有一些复杂的math运算,你将无法通过每8个字符的字母数字string(你会从36 ^ 8(78%)可能的牌中得到2 ^ 41)。 为了让你的空间更好,你可以排除一个字母(也许是O),给你97%。

考虑到你需要的字符总数,你将有一个很小的机会产生两个完全相似的号码牌。 因此,您可能会在LUA中生成数字。

你有36 ^ 8个不同的唯一号码板(2,821,109,907,456,这是很多的),即使你已经有了一百万个号码牌,你已经有一个很小的机会产生一个,大约0.000035%

当然,这一切都取决于您最终创build多less个数字板。

字母表中的8个字母 – 全部大写:

 UPDATE `tablename` SET `tablename`.`randomstring`= concat(CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25)))CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25))),CHAR(FLOOR(65 + (RAND() * 25)))); 

对于由8个随机数和大写字母组成的string,这是我的解决scheme:

 LPAD(LEFT(REPLACE(REPLACE(REPLACE(TO_BASE64(UNHEX(MD5(RAND()))), "/", ""), "+", ""), "=", ""), 8), 8, 0) 

从内到外的解释:

  1. RAND生成一个0到1之间的随机数
  2. MD5计算(1)的MD5总和,来自af和0-9的32个字符
  3. UNHEX将(2)转换为16个字节,其值从00到FF
  4. TO_BASE64将(3)编码为base64,来自az和AZ的22个字符以及0-9加“/”和“+”,随后是两个“=”
  5. 这三个REPLACE从(4)中删除“/”,“+”和“=”
  6. 如果你需要更多或更less的字符在你的随机string中,则LEFT从(5)中取前8个字符,将其改为8
  7. 如果长度小于8个字符, LPAD在(6)开头插入零; 再次,如果需要的话,将8改为别的

如果你没有一个id或种子,就像插入一个值列表:

 REPLACE(RAND(), '.', '')