生成用户友好的字母数字ID(例如业务ID,SKU)的选项有哪些?

16

以下是要求:

必须是字母数字组合,8-10个字符长度以便用户易于使用。这些将作为唯一键存储在数据库中。我正在使用Guids作为主键,因此最好提供使用Guid生成这些唯一标识符的选项。

我考虑使用基数转换器将Guid转换为8个字符的唯一字符串。

短小、轻量级算法更受欢迎,因为它会经常被调用。

5个回答

10
8 characters - perfectly random - 36^8 = 2,821,109,907,456 combinations
10 characters - perfectly random - 36^10 = 3,656,158,440,062,976 combinations
GUID's - statistically unique* - 2^128 = 340,000,000,000,000,000,000,000,000,000,000,000,000 combinations

* GUID是否100%独特?[stackoverflow]

将GUID转换为字符的问题在于,虽然GUID在统计学上是独特的,但如果取任何子集,则会降低随机性并增加碰撞的几率。您肯定不希望创建非唯一SKU。


解决方案1:

使用与对象和业务规则相关的数据创建SKU。

即,可能有一小组属性使对象唯一(自然键)。将自然键的元素组合、编码和压缩以创建SKU。通常只需要一个日期时间字段(例如CreationDate)和其他几个属性即可实现这一点。您可能会在sku创建中遇到很多问题,但sku对您的用户更加相关。

假设情况如下:

Wholesaler, product name, product version, sku
Amazon,     IPod Nano,    2.2,             AMIPDNN22
BestBuy,    Vaio,         3.2,             BEVAIO32

解决方案2:

一种方法是预留一段数字范围,然后按顺序逐个释放它们,永远不会重复使用相同的数字。但是,您仍然可能在该范围内留下空洞。通常情况下,您不需要生成足够的SKU以产生影响,但请确保您的要求允许此操作。

实现方法是在数据库中创建一个名为key的表格,并具有计数器。该计数器在事务中递增。重要的一点是,软件中的方法不是递增1,而是获取一个块。伪代码如下。

-- what the key table may look like
CREATE TABLE Keys(Name VARCHAR(10) primary key, NextID INT)
INSERT INTO Keys Values('sku',1)

// some elements of the class
public static SkuKeyGenerator 
{
    private static syncObject = new object();
    private static int nextID = 0;
    private static int maxID = 0;
    private const int amountToReserve = 100;

    public static int NextKey()
    {
        lock( syncObject )
        {
            if( nextID == maxID )
            {
                ReserveIds();
            }
            return nextID++;
        }
    }
    private static void ReserveIds()
    {
        // pseudocode - in reality I'd do this with a stored procedure inside a transaction,
        // We reserve some predefined number of keys from Keys where Name = 'sku'
        // need to run the select and update in the same transaction because this isn't the only
        // method that can use this table.
        using( Transaction trans = new Transaction() ) // pseudocode.
        {
             int currentTableValue = db.Execute(trans, "SELECT NextID FROM Keys WHERE Name = 'sku'");
             int newMaxID = currentTableValue + amountToReserve;
             db.Execute(trans, "UPDATE Keys SET NextID = @1 WHERE Name = 'sku'", newMaxID);

             trans.Commit();

             nextID = currentTableValue;
             maxID = newMaxID;
        }
    } 

这里的想法是预留足够的键,以便您的代码不经常访问数据库,因为获取键范围是一项昂贵的操作。您需要对所需保留的键的数量有一个好的想法,以平衡键丢失(应用程序重启)与过快耗尽键并返回数据库之间的关系。这个简单的实现没有办法重用丢失的键。
由于此实现依赖于数据库和事务,因此您可以同时运行多个应用程序,并且所有应用程序都可以生成唯一的键,而无需经常访问数据库。
请注意,上述内容基于企业应用架构模式(Fowler)的第222页中的“键表”。该方法通常用于生成主键,而无需使用数据库标识列,但您可以看到它如何适应您的目的。

8
你可以考虑使用 36进制,它可以包含字母和数字。建议从集合中移除字母I和O,以免与数字1和0混淆。有些人可能也会抱怨2和Z的混淆。

2
这可能是最好的选择,但不幸的是,128位GUID在36进制下仍然超过20个字符。也许GUID不是最好的起点。 - Matt Hamilton
GUID可以从我的模型对象中轻松获取,因此我相信这将非常方便。 - Mank
3
好的,你可以使用 A-Z、a-z 和 0-9,这就是 62 进制。也许这样会更好一些。 - EvilTeach
然而,人们并没有意识到大小写不同的项目是不同的,因此使用基础62会遇到问题。 - Kibbee
很长时间以来,但最终我使用了base34,并删除了一些不可读的字符。 - Mank

4
如果你想要“用户友好”的话,建议使用完整的单词而不是简单地缩写或使用字母数字混合。因此,可以尝试使用以下方式:
words = [s.strip().lower() for s in open('/usr/share/dict/canadian-english') if "'" not in s]
mod = len(words)

def main(script, guid):
    guid = hash(guid)

    print "+".join(words[(guid ** e) % mod] for e in (53, 61, 71))

if __name__ == "__main__":
    import sys
    main(*sys.argv)

这将产生类似以下的输出:

oranjestad+compressing+wellspring
padlock+discommoded+blazons
pt+olenek+renews

这很有趣。否则,只需取GUID的前8-10个字符或其哈希值(SHA1 / MD5)的前8-10个字符可能是最好的选择。

不,但前10个字符为你提供了2 ** 40种可能性的空间(大约1万亿),因此根据你要查找的标识符数量,碰撞数应该相对较低。添加唯一性约束、一个备选方案并记录碰撞。 - Aaron Maenpaa

3
最简单的方法是使用计数器,每次需要一个值时就将其加一。八位数字(左侧补零)可以提供1亿个可能的值,从00000000到99999999(尽管您可能会插入空格或连字符以增加可读性,例如000-000-00)。
如果您需要超过1亿个值,则可以增加长度或在交替位置使用字母。使用A0A0A0A0到Z9Z9Z9Z9可以提供超过45.6亿个可用值。编写代码将长整数转换为这种编码(对于最右边的数字,进行mod 10运算;除以10再进行mod 26运算以得到最右边的字母等),这是微不足道的。如果您有足够的内存,最快的方法是将计数器转换为mod 260数组,并将每个mod 260值用作两个字符字符串数组(“A0”、“A1”、“A2”等直到“A9”、“B0”、“B1”等直到“Z9”)的索引。
基于36进制的问题(在另一个回答中提到)是,您不仅需要担心类似字符的阅读者混淆(例如1与I、0与O、2与Z、5与S),还需要担心相邻字母组合可能被读者视为拼写不好听或下流的单词或缩写。

我喜欢你的建议。我唯一的担忧是在应用程序中管理全局计数器。这就是我想使用Guid作为序列的原因之一,因为顺序不是问题。 - Mank

2

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