使用 auto_increment 生成 PHP 短唯一 ID?

Posted

技术标签:

【中文标题】使用 auto_increment 生成 PHP 短唯一 ID?【英文标题】:PHP short unique ID generation using auto_increment? 【发布时间】:2010-12-11 15:12:31 【问题描述】:

我想生成一个简短的唯一 ID,而无需检查冲突。

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

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

编辑:我想从 5 个字符开始,如果我达到 6000 万个条目,然后转到 6.. 以此类推。

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

生成的字符串不应该是线性的,所以简单地将 auto_incremented ID 转换为base 36 [0-9A-Z] 有点过于简单,但我要使用类似的函数。

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

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

【问题讨论】:

【参考方案1】:

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

反转一些位(f.i. 使用 XOR,php 中的 ^) 交换位的位置 (($i & 0xc) >> 2 | ($i & 0x3) 添加一个常数值以您的最大范围为模(如果您将其与上述值相结合,则必须是两倍)

示例:此函数会将 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 的 good values,X_0、X_1 .. X_m-1 的序列将包含 0 和 m-1 之间的所有数字恰好一次。现在您可以从线性递增的索引开始,并使用 LCG 序列中的 next 值作为您的“秘密”键。

EDIT2

实施: 你可以design your own LCG parameters,但如果你弄错了,它不会覆盖整个范围(因此会有重复),所以我将在这里使用来自this paper 的一组已发布和尝试过的参数:

a = 16807, c = 0, m = 2147483647

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

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

【讨论】:

我喜欢这个主意。有一些代码给我吗? 1-60000000 映射到 5 个字符的字母数字字符串? 对不起,我现在不相信我的数学,所以它是 31 位的。对于一个 30 位的,base64_encode 之后正好有 5 个(重要的,即没有填充 = 的)字符,但我找不到为此设置的参数。 我认为这个可能适用于 30 位:a = 357913942, c = 1, m = 1073741823(如果我没记错的话,它符合***文章中的标准有一个完整的周期: m = 231-1 = 32 * 7 * 11 * 31 * 151 * 331, c = 1 所以它显然与 m 互质,并且 a-1 = 3 * 7 * 11 * 31 * 151 * 331 可以被 m 的所有素数整除...) 我读的全是数学,数学,数学.. :) 这个周末我会生成一些测试用例,嗯.. 很多测试用例。谢谢!【参考方案2】:

您可能会生成当前日期时间/随机数的 MD5 哈希并将其截断为您需要的长度(5-8 个字符)并将其存储为 id 字段。

如果您使用将这些信息存储在数据库中,则不需要使用 for 循环来进行冲突检查,但您可以只执行 select 语句 - 类似

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

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

编辑

这可能不是最好的方法。但我会试一试,所以我猜你需要的是将数字转换为唯一的短字符串,而不是按顺序排列。

我猜正如你所说,base64 编码已经完成了数字到短字符串的转换。为了避免序列问题,您可以在自动生成的 id 到某个“随机”值(唯一映射)之间进行一些映射。然后你可以对这个唯一值进行base64编码。

您可以按如下方式生成此映射。有一个临时表存储从 1 到 10,000,000 的值。以随机顺序对其进行排序并将其存储到您的 Map 表中。

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

其中 MappingTable 将具有 2 个字段 id(您的自动生成的 id 将对此进行查找)和 mappedId(您将为其生成 base64 编码)。

当您接近 10,000,000 时,您可以再次重新运行上述代码,并将临时表中的值更改为 10,000,001-20,000,000 或类似的值。

【讨论】:

如果我失败了,循环会产生另一个唯一的 id。我知道这有点过早优化的味道,但我希望有更好的方法。【参考方案3】:

您可以使用按位异或来打乱一些位:

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 |
+-----+---------+

【讨论】:

【参考方案4】:

我认为这永远不会真正安全,因为您只需要找到短唯一字符串背后的加密方法即可劫持 ID。在您的设置中检查循环中的碰撞真的有问题吗?

【讨论】:

不是现在,但在 5000 万个链接和 5 个字符的情况下,大约 80% 会变成冲突。【参考方案5】:

递增数字的 MD5 应该没问题,但我担心如果 您正在截断您的 MD5(即 通常为 128 位)低至 5-8 字符,你几乎肯定会 正在破坏它的能力 独特的签名...

完全正确。尤其是当您达到 80% 的碰撞几率时,截断的 MD5 将与任何随机数一样好,以保证其自身的唯一性,即毫无价值。

但既然您使用的是数据库,为什么不直接使用 UNIQUE INDEX 呢?这样,唯一性检查由 mysql 本身完成(以比使用循环更有效的方式)。只需尝试使用您的 MD5 生成的密钥进行 INSERT,如果失败,请重试...

【讨论】:

是的,但我仍然需要重新计算随机数并重试插入。这就是我现在所做的。我一直在寻找一种方法来自动确保我在第一次尝试时是独一无二的。 base 36 方法会在第一次尝试时执行此操作,但第一个链接将是 00000,第二个链接是 00001,依此类推。 当然,一旦我有足够的声誉...... ;) +1 表示使用 SQL 唯一索引的建议,但 -1 表示继续建议 MD5。如果要使用没有 100% 唯一值的伪随机数,您也可以使用 PHP 的 uniqid 作为唯一键(如果不是主键)或使用自动增量与外部选项(如 base64encode)混合来“屏蔽”身份证。 当然,这些都不能单独保证唯一性。您确实需要对限制范围进行排列,请参阅我的其他答案。 是的,你是对的。我不应该将其作为答案发布,但我无法对原始答案发表评论。还剩 13 分... ;-)【参考方案6】:

如果您不能使用自动递增字段,并且想要一个绝对唯一值,请使用UUID。如果您决定使用其他任何东西(除了自动增量),那么不检查冲突是很愚蠢的。

【讨论】:

【参考方案7】:

这篇博文与您所追求的内容相近。

http://kevin.vanzonneveld.net/techblog/article/create_short_ids_with_php_like_youtube_or_tinyurl/

【讨论】:

【参考方案8】:

递增数字的 MD5 应该没问题,但我担心如果您将 MD5(通常为 128 位)截断为 5-8 个字符,您几乎肯定会破坏它作为独特的签名...

【讨论】:

MD5(或任何散列)永远不能充当唯一签名。所以你的截断问题有点没有实际意义。 我不确定这如何否定我的回应。人们一直在做愚蠢的事情,这不会让哈希神奇地变得“独特”。 那么,因为碰撞甚至是可能的,那么它作为签名没有用?

以上是关于使用 auto_increment 生成 PHP 短唯一 ID?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PHP 仅检测 AUTO_INCREMENTed 值? [复制]

如何使用 PHP 为 MySQL 表中的每一行重新分配 AUTO_INCREMENT 列

Windows 上的 PHP / MySQLi:插入 MySQL innoDB 表将 AUTO_INCREMENT 列增加 2

在php中生成连续数字[关闭]

集群中 h2 和 MySQL 的休眠 id 生成器 AUTO_INCREMENT

H2 AUTO_INCREMENT PK 的 JOOQ 代码生成器,带有 japAnnotations