8

我决定使用 GUID 作为我的许多项目数据库表的主键。我认为这是一个很好的做法,尤其是考虑到可扩展性、备份和恢复。问题是我不想使用常规 GUID 并搜索替代方法。我实际上很想知道我使用什么 Pinterest 作为主键。当您查看 URL 时,您会看到如下内容:

http://pinterest.com/pin/275001120966638272/

我更喜欢数字表示,即使它是以字符串形式存储的。有什么办法可以做到这一点?

此外,youtube 还使用了另一种我无法弄清楚的散列技术:

http://www.youtube.com/watch?v=kOXFLI6fd5A

这让我想起了缩短 url 之类的方案。

我更喜欢最短的,但我知道它不能保证是唯一的。我首先想到做这样的事情:

 DateTime dt1970 = new DateTime(1970, 1, 1);
 DateTime current = DateTime.Now;
 TimeSpan span = current - dt1970;

结果示例:

1350433430523.66

打印自 1970 年以来的总毫秒数,但是如果我每秒有数十万次写入会发生什么。

我主要更喜欢非 BIGINT Auto-Increment 解决方案,因为它使使用 3rd 方工具扩展数据库以及问题较少的备份/恢复功能减少了很多麻烦,因为如果我愿意,我可以在服务器之间传输数据等。

另一种复杂的方法是针对我的应用程序定制解决方案。在数据库中,主键还将包含用户名(唯一且用户不能更改),因此我可以将名称的数值与毫秒数结合起来,这将给我一个唯一的数字字符串。因为用户插入数据的频率不高,所以保证数字ID是唯一的。我也可以删除最后 5 个数字,仍然得到一个唯一的 ID,因为我假设用户不会以每秒超过 1 的速度插入数据,但我可能不会那样做(你怎么看这个想法?)

所以我请求你的帮助。我的数据假设增长非常大,每年 2TB,每秒有数万行新行。我希望 URL 看起来尽可能“友好”,并且不希望使用“常规”GUID。

我正在使用 ASP.NET 4.5 和 MySQL 开发我的应用程序

谢谢。

4

3 回答 3

7

碰撞表

对于像 GUID 这样的 YouTube,您可以看到这个答案。他们基本上保留了他们生成的所有随机视频 ID 的数据库表。当他们请求一个新的时,他们检查表是否有任何冲突。如果他们发现碰撞,他们会尝试生成一个新的碰撞。

长主键

您可以使用long(例如275001120966638272)作为主键,但是如果您有多个生成唯一标识符的服务器,则必须以某种方式对它们进行分区或引入全局锁,因此每个服务器不会生成相同的唯一标识符。

Twitter 雪花 ID

ID分区问题的一种解决方案long是使用雪花 ID。这是Twitter用来生成其 ID 的内容。所有生成的 ID 都由以下部分组成:

  • 以毫秒为单位的纪元时间戳 - 41 位(给我们 69 年的自定义纪元)
  • 配置的机器 ID - 10 位(最多为我们提供 1024 台机器)
  • 序列号 - 12 位(每台机器的本地计数器,每 4096 翻转一次)

为将来的目的保留一个额外的位。由于 ID 使用时间戳作为第一个组件,因此它们是可按时间排序的(这对查询性能非常重要)。

Base64 编码的 GUID

您可以使用将 a 编码为 base64 字符串的ShortGuid 。GUID缺点是输出有点难看(例如00amyWGct0y_ze4lIsj2Mw),并且它区分大小写,如果您使用小写它们可能对 URL 不利。

Base32 编码的 GUID

还有GUID's 的 base32 编码,你可以看到这个答案。这些比上面的 ShortGuid 略长(例如lt7fz44kdqlu5pt7wnyzmu4ov4),但优点是它们都可以是小写的。

多重因素

我一直在考虑的一种替代方法是引入多个因素,例如如果 Pintrest 使用用户名和 ID 来获得额外的唯一性:

https://pinterest.com/some-user/1

这里的 ID1对用户来说是唯一的some-user,可以是他们发表的帖子数,即他们的下一个帖子是2. 您也可以将 YouTube 的方法与他们的视频 ID 一起使用,但特定于用户,这可能会导致一些可笑的短 URL。

于 2018-06-06T15:16:34.753 回答
6

唯一键的第一个,最简单和实用的场景是写入顺序的递增编号序列,这表示一个数据库中的记录编号,在本地范围内提供唯一编号:这是 - 经常满足 -应用程序级别的要求

接下来,通常使用基于时间和计数器串联的数值方法来确保同一货车中的并发事务在写入之前将具有唯一的 id。

当系统变得高度线程化和分布式时,比如在高度并发的情况下,是否需要放松一些约束,然后才能成为扩展的惩罚。

通用唯一标识符作为主键

是的,这是一个很好的做法。

  • 关键参考系统可以提供与底层数据库系统的独立性。
  • 当诱发场景发生时,这为数据库提供了更高级别的完整性:备份、恢复、扩展、迁移,并可能证明某些真实性。

这篇 由 Alexander Marquardt(MongoDB 的高级咨询工程师)撰写的生成用于 MongoDB 的全球唯一标识符的文章详细介绍了这个问题,并提供了有关数据库和信息学的一些见解。

UUID 是 128 位长度。它们引入了足够高的熵,以确保标签的实际唯一性。它们可以用 32 个十六进制字符串表示。足以写出几千亿十亿的十进制数。

在考虑总体原理和分析时,还会出现以下几个问题:

  1. 数据库的主键和唯一资源位置是否应该作为两个不同的实体保存?
  2. 这个编号会破坏系统中的顺序性吗?
  3. 提供机器主机号(h)、用户号(u)(t)写入索引(i) 的时间是否保证 PKhuti保持唯一?

现在考虑数据库系统:

  • 主键应保留为数字(无论是六进制)
  • 数据库系统依赖它,这意味着性能考虑。
  • 它们的大小应该是固定的,
  • 系统必须快速响应以判断它是否可能正在处理 PK。

哈希德

Youtube 的哈希技术是hashids

这是一个不错的选择:hash 是shorts,长度可以控制,字母表可以定制,它是可逆的(和对主键的简短引用一样有趣),它可以使用salt。它的设计是散列正数。

然而,它一个散列,因此存在发生冲突的可能性。可以检测到它们:在存储它们之前违反了唯一约束,在这种情况下,应该再次运行。

考虑对这个答案的评论,以确定从缩短的 sha1+b64 配方中可以获得多少熵。 为了预测碰撞场景,需要估计数据库的未来维度,即潜在的记录数。推荐阅读:Z.Bloom,一个ID需要多长时间?

自纪元以来的毫秒数

引用自上一篇文章,该文章以漂亮的合成风格提供了手头问题的大部分答案

但是,自 1970 年以来,您可能不需要每次都进行编码。如果您只对保持最近的记录彼此靠近感兴趣,您只需要足够的值来确保您没有更多具有相同前缀的值,而不是您的数据库可以一次缓存

于 2018-06-13T05:06:02.953 回答
1

您可以做的是通过将所有字母转换为 guid 中的数字,将 GUID 转换为仅数字。这是一个看起来像的例子。它有点长,但如果这不是问题,这可能是生成密钥的一种方法。

1004234499987310234371029731000544986101469898102

这是我用来生成上面字符串的代码。但是我可能会建议您使用长主键,尽管它可能会有点痛苦,但它可能是比下面的函数更安全的方法。

    string generateKey()
    {
        Guid guid = Guid.NewGuid();
        string newKey = "";
        foreach(char c in guid.ToString().Replace("-", "").ToCharArray())
        {
            if(char.IsLetter(c))
            {
                newKey += (int)c;
            }
            else
            {
                newKey += c;
            }
        }
        return newKey;
    }

编辑:

我做了一些测试,只取了前 20 个数字,在 5000000 个生成的密钥中,4999978 是唯一的。但是当使用 25 个第一个数字时,它是 5000000 中的 5000000。如果使用这种方法,我建议您进行更多测试。

于 2018-06-12T08:42:12.197 回答