135

我正在开发一款在某些时候涉及车辆的游戏。我有一个名为“vehicles”的 MySQL 表,其中包含有关车辆的数据,包括存储车辆牌照的列“plate”。

现在到了我遇到问题的部分。在创建新车之前,我需要找到一个未使用的车牌 - 它应该是一个字母数字 8 字符的随机字符串。我是如何做到这一点的,是在 Lua 中使用 while 循环(我正在编程的语言)来生成字符串并查询数据库以查看它是否被使用。然而,随着车辆数量的增加,我预计这会变得更加低效,就像现在一样。因此,我决定尝试使用 MySQL 查询来解决这个问题。

我需要的查询应该只生成一个 8 字符的字母数字字符串,该字符串不在表中。我再次想到了 generate&check 循环方法,但我并没有将这个问题限制在这个问题上,以防万一有更有效的方法。我已经能够通过定义一个包含所有允许字符的字符串并随机对其进行子串化来生成字符串,仅此而已。

任何帮助表示赞赏。

4

25 回答 25

155

我不会担心碰撞的可能性。只需生成一个随机字符串并检查它是否存在。如果是这样,请再试一次,除非您已经分配了大量的盘子,否则您不需要再做几次。

在纯 (My)SQL 中生成 8 个字符长的伪随机字符串的另一种解决方案:

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

由于这篇文章受到了意想不到的关注,让我强调一下 ADTC 的评论:上面的代码非常愚蠢,并且产生了连续的数字。

对于稍微不那么愚蠢的随机性,请尝试这样的事情:

SELECT LEFT(MD5(RAND()), 8)

对于真正的(加密安全的)随机性,使用RANDOM_BYTES()而不是RAND()(但我会考虑将此逻辑上移到应用程序层)。

于 2013-05-24T15:16:36.020 回答
93

这个问题由两个非常不同的子问题组成:

  • 字符串必须是看似随机的
  • 字符串必须是唯一的

虽然随机性很容易实现,但没有重试循环的唯一性则不然。这使我们首先专注于独特性。使用 可以轻松实现非随机唯一性AUTO_INCREMENT。因此,使用保持唯一性的伪随机变换就可以了:

  • @paul 建议使用哈希
  • AES-encrypt 也适合
  • 但有一个很好的:RAND(N)它本身!

由同一种子创建的随机数序列保证是

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

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

例如 Assuminid是一个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;
于 2013-05-24T15:21:09.457 回答
57

如何计算顺序整数的 MD5(或其他)哈希,然后取前 8 个字符。

IE

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

等等

警告:我不知道在碰撞之前可以分配多少(但这将是一个已知且恒定的值)。

编辑:这现在是一个旧答案,但随着时间的推移,我再次看到它,所以,从观察...

所有数字的机会 = 2.35%

所有字母的机会 = 0.05%

MD5(82945) = "7b763dcb..." 时的第一次碰撞(结果与 MD5(25302) 相同)

于 2013-05-24T15:05:43.077 回答
45

创建一个随机字符串

这是一个 MySQL 函数,用于创建给定长度的随机字符串。

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 个字符的字符串。

您可以自定义@allowedChars.

不能保证唯一性 - 正如您将在其他解决方案的评论中看到的那样,这是不可能的。相反,您需要生成一个字符串,检查它是否已被使用,如果是,请重试。


检查随机字符串是否已被使用

如果我们想将碰撞检查代码排除在应用程序之外,我们可以创建一个触发器:

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 ;
于 2016-11-30T10:41:41.483 回答
28

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

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;

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

于 2013-05-24T15:07:00.793 回答
25

这是另一种生成随机字符串的方法:

SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 8) AS myrandomstring

于 2017-12-19T10:10:15.780 回答
17

你可以使用 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;
于 2013-05-24T15:11:36.273 回答
15

You can generate a random alphanumeric string with:

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

You can use it in a BEFORE INSERT trigger and check for a duplicate in a while loop:

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 ;

Now just insert your data like

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

And the trigger will generate a value for the plate column.

(sqlfiddle demo)

That works this way if the column allows NULLs. If you want it to be NOT NULL you would need to define a default value

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

You can also use any other random string generating algorithm in the trigger if uppercase alphanumerics isn't what you want. But the trigger will take care of uniqueness.

于 2016-09-10T00:02:09.317 回答
8

对于由 8 个随机数和大小写字母组成的字符串,这是我的解决方案:

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

由内而外解释:

  1. RAND生成一个介于 0 和 1 之间的随机数
  2. MD5计算 (1)、来自 af 和 0-9 的 32 个字符的 MD5 和
  3. UNHEX将 (2) 转换为 16 个字节,其值从 00 到 FF
  4. TO_BASE64将 (3) 编码为 base64,从 az 和 AZ 和 0-9 开始的 22 个字符加上“/”和“+”,后跟两个“=”
  5. 这三个REPLACEs 从 (4) 中删除了“/”、“+”和“=”字符
  6. LEFT取 (5) 中的前 8 个字符,如果您在随机字符串中需要更多或更少的字符,请将 8 更改为其他字符
  7. LPAD如果 (6) 的长度少于 8 个字符,则在 (6) 的开头插入零;再次,如果需要,将 8 更改为其他内容
于 2017-08-22T14:08:34.240 回答
7

要生成随机字符串,您可以使用:

SUBSTRING(MD5(RAND()) FROM 1 FOR 8)

你收到这样的东西:

353E50CC

于 2018-04-14T05:26:01.353 回答
5

我使用来自另一列的数据来生成“散列”或唯一字符串

UPDATE table_name SET column_name = Right( MD5(another_column_with_data), 8 )
于 2016-10-05T20:37:30.640 回答
4

字母表中的 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))));
于 2016-10-12T04:29:02.890 回答
3

如果您没有 id 或种子,就像它在插入中的值列表一样:

REPLACE(RAND(), '.', '')
于 2017-09-19T17:36:57.633 回答
2

获取包含大小写字母和数字的随机 10 个字符串的简单有效的解决方案:

select substring(base64_encode(md5(rand())) from 1+rand()*4 for 10);
于 2020-03-01T21:18:44.013 回答
2

要创建一个随机的10 位字母数字,不包括相似字符 01oOlI:

LPAD(LEFT(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(TO_BASE64(UNHEX(MD5(RAND()))), "/", ""), "+", ""), "=", ""), "O", ""), "l", ""), "I", ""), "1", ""), "0", ""), "o", ""), 10), 10, 0)

这正是我创建优惠券代码所需要的。在将其输入凭证代码表单时,混淆字符被删除以减少错误。

希望这对某人有所帮助,基于Jan Uhlig 的精彩回答

请参阅 Jan 的答案以了解此代码的工作原理。

于 2020-02-17T10:35:35.287 回答
1

考虑到您需要的字符总数,您生成两个完全相同的车牌的机会非常小。因此,您可能可以在 LUA 中生成数字。

你有 36^8 个不同的唯一车牌(2,821,109,907,456,很多),即使你已经有一百万个车牌,你也有很小的机会生成你已经拥有的车牌,大约 0.000035%

当然,这完全取决于您最终将创建多少个车牌。

于 2013-05-24T15:12:52.877 回答
1

如果您对“随机”但完全可预测的车牌没问题,您可以使用线性反馈移位寄存器来选择下一个车牌号码 - 它保证在重复之前通过每个号码。但是,如果没有一些复杂的数学运算,您将无法遍历每个 8 个字符的字母数字字符串(您将在 36^8 (78%) 个可能的盘子中得到 2^41 个)。为了更好地填充你的空间,你可以从盘子中排除一个字母(可能是 O),给你 97%。

于 2013-05-24T15:18:27.713 回答
1

此函数根据您的输入长度和允许的字符生成一个随机字符串,如下所示:

SELECT str_rand(8, '23456789abcdefghijkmnpqrstuvwxyz');

功能代码:

DROP FUNCTION IF EXISTS str_rand;

DELIMITER //

CREATE FUNCTION str_rand(
    u_count INT UNSIGNED,
    v_chars TEXT
)
RETURNS TEXT
NOT DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
COMMENT ''
BEGIN
    DECLARE v_retval TEXT DEFAULT '';
    DECLARE u_pos    INT UNSIGNED;
    DECLARE u        INT UNSIGNED;

    SET u = LENGTH(v_chars);
    WHILE u_count > 0
    DO
      SET u_pos = 1 + FLOOR(RAND() * u);
      SET v_retval = CONCAT(v_retval, MID(v_chars, u_pos, 1));
      SET u_count = u_count - 1;
    END WHILE;

    RETURN v_retval;
END;
//
DELIMITER ;

此代码基于“Ross Smith II”发送的随机字符串函数

于 2019-05-21T12:52:42.297 回答
1

生成 8 个字符的密钥

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

如何为我的 MySql 表列之一生成唯一的随机字符串?

于 2019-08-30T10:15:32.293 回答
1
UPPER(HEX(UUID_SHORT()))

为您提供一个唯一的 16 个字符的字母数字字符串。它有一些不太可能的警告,请参阅https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_uuid-short

“下一个”值通常是可以预测的:

mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000006          |
+--------------------------+

mysql> SELECT UPPER(HEX(UUID_SHORT()));
+--------------------------+
| UPPER(HEX(UUID_SHORT())) |
+--------------------------+
| 161AA3FA5000007          |
+--------------------------+

转换为 BASE64 可以将字符串减少到 11 个字符:

https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_to-base64

mysql> SELECT TO_BASE64(UNHEX(HEX(UUID_SHORT())));
+-------------------------------------+
| TO_BASE64(UNHEX(HEX(UUID_SHORT()))) |
+-------------------------------------+
| AWGqP6UAABA=                        |
+-------------------------------------+

那是 12 个字符,去掉“=”会得到 11 个字符。

这些可能使它不适合您的使用:“下一个”板有点可预测。字符串中可以有一些标点符号 ( +, /)。可能包括小写字母。

于 2021-12-06T20:03:26.303 回答
0

生成唯一编号的简单方法

set @i = 0;
update vehicles set plate = CONCAT(@i:=@i+1, ROUND(RAND() * 1000)) 
order by rand();
于 2018-10-12T07:31:50.713 回答
0

这项工作形成了我,生成 6 位数字并在 MySQL 中更新:

产生:

SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 6)

更新:

UPDATE table_name 
SET column_name = SUBSTRING(MD5(RAND()) FROM 1 FOR 6) 
WHERE id = x12
于 2021-12-10T10:51:14.413 回答
0
DELIMITER $$

USE `temp` $$

DROP PROCEDURE IF EXISTS `GenerateUniqueValue`$$

CREATE PROCEDURE `GenerateUniqueValue`(IN tableName VARCHAR(255),IN columnName VARCHAR(255)) 
BEGIN
    DECLARE uniqueValue VARCHAR(8) DEFAULT "";
    WHILE LENGTH(uniqueValue) = 0 DO
        SELECT CONCAT(SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
                SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1)
                ) INTO @newUniqueValue;
        SET @rcount = -1;
        SET @query=CONCAT('SELECT COUNT(*) INTO @rcount FROM  ',tableName,' WHERE ',columnName,'  like ''',@newUniqueValue,'''');
        PREPARE stmt FROM  @query;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    IF @rcount = 0 THEN
            SET uniqueValue = @newUniqueValue ;
        END IF ;
    END WHILE ;
    SELECT uniqueValue;
    END$$

DELIMITER ;

使用此存储过程并每次都使用它

Call GenerateUniqueValue('tableName','columnName')
于 2018-04-13T07:00:13.357 回答
0

我一直在寻找类似的东西,我决定制作自己的版本,如果需要,您还可以指定不同的种子(字符列表)作为参数:

CREATE FUNCTION `random_string`(length SMALLINT(3), seed VARCHAR(255)) RETURNS varchar(255) CHARSET utf8
    NO SQL
BEGIN
    SET @output = '';

    IF seed IS NULL OR seed = '' THEN SET seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; END IF;

    SET @rnd_multiplier = LENGTH(seed);

    WHILE LENGTH(@output) < length DO
        # Select random character and add to output
        SET @output = CONCAT(@output, SUBSTRING(seed, RAND() * (@rnd_multiplier + 1), 1));
    END WHILE;

    RETURN @output;
END

可用作:

SELECT random_string(10, '')

这将使用大小写字符+数字的内置种子。NULL 也将是值而不是 ''。

但是可以在调用时指定自定义种子:

SELECT random_string(10, '1234')
于 2019-09-11T09:47:57.377 回答
0

SQL 触发器是复杂且资源密集型的。针对基于 MySQL“触发器”的解决方案,这里有一个更简单的解决方案。

  1. 在 MySQL 表列上创建一个 UNIQUE INDEX,该列将保存车辆牌照字符串。这将确保只有唯一值进入。
  2. 只需在 Lua(或任何其他编程语言,如 ASP、PHP、Java 等)中生成标准的字母数字随机字符串
  3. 使用生成的字符串执行 INSERT 语句,并有错误捕获代码来解析失败(在违反 UNIQUE INDEX 的情况下)
  4. 如果 INSERT 失败,则生成一个新的随机字符串并重新插入。8 个字符的长度本身很难重复,一旦在表中找到生成另一个字符,几乎不可能再重复。

这将在 DB Server 上更轻、更高效。

这是 PHP 中的示例(伪)代码:

function refercode()
{
    $string = '';
    $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $max = strlen($characters) - 1;
    for ($i = 0; $i < 8; $i++) {
        $string .= $characters[mt_rand(0, $max)];
    }
    $refer = "select * from vehicles where refer_code = '".$string."' ";
    $coderefertest = mysqli_query($con,$refer);

    if(mysqli_num_rows($coderefertest)>0)
    {
        return refercode();
    }
    else
    {
        return $string;
    }
}
$refer_by = refercode();
于 2021-11-27T13:43:57.037 回答