我想生成一个简短的、唯一的 ID,而不必检查冲突。

我目前正在做类似的事情,但是我当前生成的 ID 是随机的,并且在循环中检查冲突很烦人,并且如果记录数量显着增加,将会变得昂贵。

通常担心冲突不是问题,但我想要生成的唯一 ID 是一个由 5-8 个字符组成的唯一短字符串,字母数字,就像tinyurl 一样。

编辑:我想从 5 个字符开始,如果我达到 6000 万个条目,则转到 6..等等等等。

为此,我想我可以使用对用户隐藏的 auto_increment 值,并用 MD5 或其他一些方法来生成唯一的字符串。

生成的字符串不应看起来是线性的,因此只需将 auto_incremented ID 转换为 base 36 [0-9A-Z] 有点太简单了,但类似的函数就是我要处理的地方。

编辑:安全性不是问题,因为这不会用于保护信息。它只是更长字符串的快捷方式。谢谢。

感谢您的建议,并对延迟表示歉意。牙医..

有帮助吗?

解决方案

您需要一些通过构造正确的东西,即置换函数:这是一个将一个整数(顺序计数器)一对一可逆映射到另一个整数的函数。一些示例(这些的任意组合也应该有效):

  • 反转一些位(例如使用 XOR,PHP 中的 ^)
  • 交换位的位置 (($i & 0xc) >> 2 | ($i & 0x3) << 2),或者只是反转所有位的顺序
  • 添加以最大范围为模的常数值(如果将其与上面的值结合起来,则必须是两倍)

例子:该函数将转换 0, 1, 2, 3, 5, ..分为 13, 4, 12, 7, 15, ..对于 15 以内的数字:

$i=($input+97) & 0xf;
$result=((($i&0x1) << 3) + (($i&0xe) >> 1)) ^ 0x5;

编辑

一种更简单的方法是使用线性同余生成器(LCG,通常用于生成随机数),它由以下形式的公式定义:

X_n+1 = (a * X_n + c) mod m

为了 良好的价值观 a、c 和 m 的序列 X_0、X_1 ..X_m-1 将仅包含 0 到 m-1 之间的所有数字一次。现在您可以从线性增加的索引开始,并使用 下一个 LCG 序列中的值作为您的“秘密”密钥。

编辑2

执行:你可以 设计您自己的 LCG 参数, ,但如果你弄错了,它不会覆盖整个范围(因此有重复项),所以我将使用这里发布的和尝试过的参数集 这张纸:

a = 16807, c = 0, m = 2147483647

这给你的范围是 2**31。使用 pack(),您可以将结果整数作为字符串获取,base64_encode() 使其成为可读字符串(最多 6 个有效字符,每个字节 6 位),因此这可以是您的函数:

substr(base64_encode(pack("l", (16807 * $index) % 2147483647)), 0, 6)

其他提示

您也许可以生成当前日期时间/随机数的MD5哈希值,并截断到你需要的长度(5-8个字符),并将其存储作为ID字段。

如果您正在使用在数据库中存储这些信息,你不需要使用一个for循环做碰撞检查,但你可以只是做一个选择语句 - 类似

SELECT count(1) c FROM Table WHERE id = :id

其中:ID将是新产生的ID。如果c是大于0,那么你知道它已经存在。

修改

这可能未必是去了解它的最佳方式。但我给它一个镜头,所以我想你需要的是好歹转换数字成一个独特的短串的,这是不按顺序排列。

我想你说,base64编码已经做短串转换的数目。为了避免序列问题,你可以有一些“随机”值(唯一映射)的自动生成的ID之间的一些映射。然后,你可以Base64编码此唯一值。

如下时可能产生这种映射。 1000 - 1有一个临时表中存储值。排序是按随机顺序,并将其存储到您映射表。

INSERT INTO MappingTable (mappedId) SELECT values FROM TemporaryTable ORDER BY RAND()

在哪里MappingTable将有2场身份证(您自动生成的ID会抬头对这个)和mappedId(这是什么,你会产生base64编码)。

当你接近1000万,你可以重新运行上面的代码,并与10,000,001-20,000,000或类似的东西改变在临时表中的值。

可以使用按位XOR来加扰一些位:

select thefield ^ 377 from thetable;

+-----+---------+
| a   | a ^ 377 |
+-----+---------+
| 154 |     483 |
| 152 |     481 |
|  69 |     316 |
|  35 |     346 |
|  72 |     305 |
| 139 |     498 |
|  96 |     281 |
|  31 |     358 |
|  11 |     370 |
| 127 |     262 |
+-----+---------+

我想,这绝不会是真正安全的,因为你只需要找到短唯一的字符串后面的加密方法劫持的ID。在循环中碰撞检测真的在你的设置有问题?

  

一个递增数的MD5   应该是很好,但我担心,如果   你截断你的MD5(这是   通常128位)下降到5-8   字符,你几乎肯定会   会损害它作为能力   一个独特的签名...

完全真实的。特别是如果你达到80%的碰撞几率截断的MD5将是不比任何人的随机数,以保证自身的独特性,即不值钱。

但是,由于您使用的是数据库无论如何,为什么不直接使用一个唯一索引?通过这种方式,uniquness检查是(比使用一个循环更加有效的方式)由MySQL本身。刚刚尝试做INSERT与MD5生成的密钥,如果失败,再尝试......

如果您无法使用自动递增字段,并希望有一个绝对独特的价值,使用的 UUID 。如果您决定使用其他任何东西(除了自动递增),你将是愚蠢的不检查碰撞。

一个递增数的MD5应该罚款,但我担心的是,如果你截断你的MD5(这通常是128位)下降到5-8个字符,你几乎肯定会破坏它作为一种能力唯一签名...

scroll top