SQL数据库中主键、自增和UUID的最佳实践

54

我们正在为用户实体设计一个表格。唯一的非平凡要求是该用户实体应有一个永久URL(例如他们的个人资料)。互联网上有很多关于int / long与UUID的内容,但对我来说仍然不清楚。

  1. 考虑到个人资料中包含私人信息,将可预测的ID嵌入URL不是一个好主意。我正确吗?
  2. 为了满足第一个要求,我可以将主键设置为UUID并将其嵌入URL。但是有两个问题。 我应该担心 UUID作为主键在索引,插入,选择和连接方面带来的性能损失吗?

基于以上情况,以下哪种做法更好?

CREATE TABLE users(
  pk UUID NOT NULL,
  .....
  PRIMARY KEY(pk)
);
CREATE TABLE users(
  pk INT NOT NULL AUTO_INCREMENT,
  id UUID NOT NULL,
  .....
  PRIMARY KEY(pk),
  UNIQUE(id)
);
6个回答

65

实际上这是一个选择问题,从我的角度来看,这个问题可能引起基于观点的回答。我经常做的事情是在自动增量列上创建主键(我称之为技术键),即使它是冗余的,也可以将其保持一致性在数据库中,允许“主键”在设计阶段出现错误时更改,并且在任何其他表中指向该键的外键约束指向的情况下允许消耗更少的空间,并且我使候选键唯一且不为空。

技术键通常不会向最终用户显示,除非您决定这样做。这对于其他您仅在数据库级别保存的技术列也适用,以便满足您可能需要的任何目的,例如修改日期、创建日期、版本、更改记录的用户等。

在这种情况下,我会选择您的第二个选项,但稍作修改:

CREATE TABLE users(
  pk INT NOT NULL AUTO_INCREMENT,
  id UUID NOT NULL,
  .....
  PRIMARY KEY(pk),
  UNIQUE(id)
);

3
@Kamil,当存在关系时,应该使用auto-inc作为FK吗?但这是否意味着简单查询会有额外的连接?例如1对多的客户-付款关系,意味着要获取客户Key的付款,我们将在客户上使用auto-inc加入付款,其中customerKey = req中的key,而不是仅查询付款表,其中customerKey =请求中的key。 - Mu-Majid
@Mu-Majid。由于这个问题现在有点老了,但我和你一样在思考同样的问题,你是否已经找到了适合你需求的方法,或者你使用了这种方法? - Vito

41

这个问题是非常基于观点的,以下是我的看法。

我的建议是使用第二个方案,也就是使用独立的UUID作为主键。原因如下:

  • 主键是唯一的,而且不会对外公开。
  • UUID是唯一的,但可能会被公开。

如果出现任何情况导致UUID泄露,你需要更改它。更改主键可能很昂贵,并且会带来很多副作用。如果UUID与主键分离,那么更改UUID(虽然不是微不足道的)将产生较少的后果。


5
数字ID或UUID不应该是机密的。安全性不应该基于无法猜测的ID,访问权限应该被检查。 - ymajoros
8
并非所有资源都是完全私有的。例如,某些资源可以被“任何拥有链接的人”访问。谷歌的Docs和Sheets就是这样做的。在这种情况下,自动增加的ID应该保密,以防止fusking式攻击来发现文档。在这种情况下,UUID会很好,因为它们没有真正的模式可供猜测,因此找到它们需要耗费时间。因此,它们提供了一层可接受的保护,同时仍然易于访问。安全性不仅是开/关的问题。安全性始终涉及各种风险和可用性权衡的妥协层次。 - Joel Mellon
1
@JoelMellon,URL并不是安全性的关键所在。使其安全的是谷歌身份验证与您授权的访问权限的结合。当然,如果您将文档设为公开,那么它就会变得公开,但没有人强迫您这样做。使用增量ID不会降低安全性,但会更清晰地表明公共文档是公开的事实。 - ymajoros
3
我认为@JoelMellon想要表达的是,由于交易记录可以通过连续的数字ID公开访问,你可能不希望外部用户以某种方式确定你系统中的交易记录数量。虽然这些资源是公开的,但没有人能够确切知道你拥有多少。 - francis94c
@ymajoros 使用增量ID会使大规模扫描文档变得更加困难,不是吗? - El Mac

13
使用UUID作为pk:首先问题是,UUID的存储空间比int大9倍。其次,如果你需要更频繁地按pk排序,就不要考虑UUID了。将UUID用作pk不会影响where条件或其他操作的时间复杂度,除非是sort使用int作为pk:易于被猜测。暴力攻击者会喜欢这个。这是唯一的问题,但也是最大的问题。 int用作pk,同时保留UUID:如果UUID不是pk,那么使用UUID进行搜索时时间复杂度会增加。即使所有关系都由int维护,但当您按UUID搜索时,它仍需要时间。由于关系是在int上维护的,因此这里解决了9x存储问题。

7
永远不会有人按UUID排序。唯一的过滤器将永远是“where user.uuid = some_uuid”。 关于索引选择(where),我并不认为UUID会更慢,因为所有值都会在表中完美分布。自动递增会有较差的分布-所有最近的记录都会被聚集在一起,从而降低索引性能。 对于存储大小,UUID只比bigint大两倍。 - David Hempy
UUID是128位长,是BIGINT的两倍大小,是INTEGER类型的四倍大小。而且将UUID作为主键可能会导致性能问题,特别是在跨多个表进行百万行连接时。 - Rodolfo Maayos

11
我发现了一篇很好的文章,解释了使用UUID作为主键的优缺点。最后,它建议同时使用递增整数作为主键和UUID作为对外界的标识。不要将你的主键暴露给外部。
在多个上下文中使用的一个解决方案是,在数据库内部使用小型、高效的数字顺序键(int或bigint)来管理数据关系,然后添加一个用UUID填充的列(可能是insert触发器)。在数据库本身的范围内,可以使用通常的主键和外键来管理关系。
但当需要向外界(即使是另一个内部系统)公开数据引用时,必须仅依赖于UUID。这样,如果您必须更改内部主键,您可以确保仅限于一个数据库范围。(注意:这是完全错误的,正如Chris所观察到的)
我们在另一家公司用于客户数据时采用了这种策略,只是为了避免“可猜测”的问题。(注意:避免不同于防止,见下文)
在另一种情况下,我们会生成一个文本“slug”(例如在博客文章中),使URL更加友好。如果我们有重复,我们就会附加一个哈希值。
即使作为“次要主键”,使用字符串形式的UUID的天真用法也是错误的:使用内置的数据库机制,因为值存储为8字节整数。
使用整数是因为它们高效。此外,还要使用数据库实现的UUIDs来混淆任何对外部引用。
参考链接:https://tomharrisonjr.com/uuid-or-guid-as-primary-keys-be-careful-7b2aa3dcb439

8
不要将其作为数据库的主键:如果您想更改数据库技术,这将会导致未来的问题。如果将其设置为递增数字,竞争对手将知道您有多少用户以及您添加新用户的速度。

4

原则是要在以下内容之间保持清晰的分离:

  • 业务价值(即使是一些UUID也可以表示)

  • 技术价值(如主键)

例如,如果您想通过其ID将某个记录映射到某个映射,这种映射是业务价值,因此为了保持上述分离,您需要使用专用字段(如UUID)而不是技术主键。


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