PHP/MySQL - 创建唯一随机字符串的最佳方法是什么?

26

如何在MySQL中创建一个随机唯一字符串?

当我需要在PHP中创建一个随机字符串时,我使用这个函数:

public function generateString($length)
{   
    $charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    for($i=0; $i<$length; $i++) 
        $key .= $charset[(mt_rand(0,(strlen($charset)-1)))]; 

    return $key;
}
然后我会将生成的字符串存储到MySQL数据库中。
如何确保生成的随机字符串对于数据库中创建的其他条目的所有随机字符串都是唯一的?也许像这样做?
while(++$i < 100)
{
  //query db with random key to see if there is a match

  //if no match found break out of loop
  break;

}

这看起来有些凌乱和冗长,并且我可能需要多次访问数据库。 我如何快速确保我的新随机字符串是唯一的?


可能是PHP中的短唯一标识符的重复内容。 - Mark Biek
10个回答

17
为什么不使用内置函数生成唯一标识符呢?这样就不必担心重复了。PHP和MySQL都有自己的内置函数。 PHP: uniqid() MySQL: UUID()

默认情况下,uniquid会生成13个字符,但我只需要10个。如果我去掉最后3个字符,那么是否会留下重复的可能性? - John
为什么必须是10个字符? - Mark Biek
另外,这个问题有一些不错的答案。https://dev59.com/X3VC5IYBdhLWcg3wZwNT - Mark Biek
这是我正在构建的Web应用程序中的要求。 - John
1
那么我认为你只能自己编写解决方案并手动检查重复项。 - Mark Biek
尽管这些ID是唯一的,但它们并不十分随机。 - Barmar

9
假设使用字符集 a-z,A-Z,0-9 的 10 个字符意味着有 (26 + 26 + 10)10 = 8.39299366 × 1017 种可能的组合。要计算发生碰撞的概率...只需将前面提到的数字除以 x。因此,我不必担心重复获取相同的字符串。即使再次获得相同的字符串,我也会在循环中再次运行函数,唯一的退出条件是找到一个唯一的字符串。

3
你的碰撞概率计算并不完全正确 - 你正在计算一个元素之间的碰撞而非集合之间的碰撞。对于小型集合,你的碰撞概率大约是n(n-1)/N,其中n是集合的大小,N是8.329x10^17。由此得出结论,当数据量达到约为sqrt(N)(即10^8)时,碰撞概率就变得不容忽视了。如果你有大量(但不是过度巨大的)数据,则很可能会发生碰撞。在这种情况下,最好的解决方案是略微增加密钥长度,或在使用该值之前进行检查。 - Michael Anderson
@Michael:我认为,考虑到我已经有一百万个唯一字符串,发生冲突的概率仍然是万分之一(1000000/62^10)。 - Salman A
1
我同意,如果你已经有了1M个唯一的字符串,那么添加另一个字符串时碰撞的机会是1M/(10^17)。但你必须重复这个过程1M次。这意味着你总体上发生碰撞的机会大约是 1M x (1M/(10^17)) - Michael Anderson
@SalmanA 你会加一个最大重试次数的参数吗?例如,最多尝试生成数字 1000 次?还是将代码放在无限循环中? - tonix
我会将它放在一个无限循环中,如果循环执行了1000次甚至3次,就抛出一个异常。 - Salman A

3

SET rand_str = SUBSTRING(MD5(NOW()),1,$LENGTH); -- 其中LENGTH根据MD5算法的要求可以取1到32之间的整数。

以下是一些示例:

SET rand_str = SUBSTRING(MD5(NOW()),1,5); -- 生成由5个字符组成的字符串。

SET rand_str = SUBSTRING(MD5(NOW()),1,15); -- 生成由15个字符组成的字符串。


1
我建议你在数据库中将该id的列设为唯一。这样,您可以执行以下操作以防止冲突:
    $row_count = 0;
    while ($row_count == 0) {
        error_reporting(0);
        $id_string = substr(uniqid(), 0, 10);

        $sql = "UPDATE <table> SET unique_id = :unique_id WHERE <something true>";
        $query = $this->db->prepare($sql);
        $query->execute(array(':unique_id' => $unique_id));
        $row_count = $query->rowCount();
    }

当然,查询可能需要尝试多次,但这样你就知道它在您的数据库中是保证唯一的。error_reporting(0)行在其中以抑制可能打开的任何警告。PHP的uniqid()也不是最独特的生成器,但您可以轻松地将其替换为自己的生成器,或者只是承担偶尔发生冲突的风险。

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')

0

独特的随机字符串可以用作字符键或令牌来标识数据库记录并检查数据库表,并提供带有存储$refer_by变量的唯一键。

define('DB_SERVER', "localhost");
define('DB_USER', "root");
define('DB_PASS', "");
define('DB_DATABASE', "student");
$con = mysqli_connect(DB_SERVER, DB_USER, DB_PASS, DB_DATABASE);

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

    if(mysqli_num_rows($coderefertest)>0)
    {
        return refercode();
    }
    else
    {
        return $string;
    }
}
$refer_by = refercode();

https://meta.stackoverflow.com/questions/300837/what-comment-should-i-add-to-code-only-answers - Razvan Dumitru

0

我通常使用:

SELECT LEFT(MD5(id), 8)

根据需求的变量:

SELECT LEFT(UUID(), 8)

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

0

如果您想将这些字符串用于安全目的,应该使用openssl_random_pseudo_bytes,它会告诉您PHP是否能够使用强算法来生成它:

但是输出需要进行一些清理。请查看this question以获取更多信息。


0

幸运的是,数据库已经具备创建唯一ID(数字)的能力 - 我建议采用我们采取的方法,即创建一个逐渐增加的数字ID和一个字母数字ID之间的双向转换。这样做是为了确保字母数字“随机”版本也是唯一的,而无需显式地测试它们。事实上,我只在数据库中存储数字版本(因为您可以通过SERIAL列免费获得它),并且只打印字母版本。

此示例生成七字节的ID,但该方法可以轻松调整以适应几乎任何情况。

参见:如何在MySQL中生成唯一ID?


0

看一下uniqid函数和pecl uuid extension。 任何一个都可以用作生成GUID的基础,但如果你计划有一个集群,你需要确保有额外的东西来确保两个服务器不会生成相同的ID。 为ID添加前缀或后缀的每个服务器配置就足以解决这个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接