语境
我正在设计一个数据库(在PostgreSQL 9.6上),该数据库将存储来自分布式应用程序的数据。由于应用程序的分布式性质,SERIAL
由于潜在的竞争条件,我不能使用自动增量整数()作为主键。
自然的解决方案是使用UUID或全局唯一标识符。Postgres带有内置UUID
类型,非常适合。
UUID存在的问题与调试有关:这是一个非人类友好的字符串。标识符ff53e96d-5fd7-4450-bc99-111b91875ec5
什么都没告诉我,而ACC-f8kJd9xKCd
虽然不能保证唯一,但告诉我我正在处理一个ACC
对象。
从编程的角度来看,调试与几个不同对象相关的应用程序查询是很常见的。假设程序员错误地ACC
在ORD
(order)表中搜索(account)对象。使用人类可读的标识符,程序员可以立即识别问题,而在使用UUID时,他将花费一些时间来找出问题所在。
我不需要UUID的“保证”唯一性;我确实需要一些空间来生成没有冲突的密钥,但是UUID太过分了。同样,在最坏的情况下,如果发生冲突(数据库拒绝它并且应用程序可以恢复),也不会是世界末日。因此,考虑到折衷,较小但对人类友好的标识符将是我的用例的理想解决方案。
识别应用程序对象
我想出的标识符具有以下格式:{domain}-{string}
,其中{domain}
用对象域(帐户,订单,产品)代替,并且{string}
是随机生成的字符串。在某些情况下,甚至可能{sub-domain}
在随机字符串之前插入一个。让我们忽略的长度{domain}
,并{string}
为保证唯一性的目的。
如果该格式有助于索引/查询性能,则可以具有固定大小。
问题
知道:
- 我想使用类似格式的主键
ACC-f8kJd9xKCd
。 - 这些主键将成为几个表的一部分。
- 所有这些键都将在6NF数据库上的多个联接/关系中使用。
- 大多数表的大小将为中到大(平均约100万行;最大的约1亿行)。
关于性能,什么是存储此密钥的最佳方法?
以下是四种可能的解决方案,但是由于我对数据库的经验很少,因此我不确定哪种数据库(最好)是最好的。
考虑的解决方案
1.存储为字符串(VARCHAR
)
(Postgres CHAR(n)
和和之间没有区别VARCHAR(n)
,因此我忽略了CHAR
)。
经过一些研究,我发现,与的字符串比较VARCHAR
(特别是在join操作上)比使用慢INTEGER
。这是有道理的,但是我应该在这种规模上担心吗?
2.存储为二进制(bytea
)
与Postgres不同,MySQL没有本机UUID
类型。有几篇文章解释了如何使用16字节BINARY
字段而不是36 字节字段来存储UUID VARCHAR
。这些帖子使我想到了将密钥存储为二进制文件(bytea
在Postgres上)。
这样可以节省大小,但我更关心性能。我很少能找到解释比较快速的解释:二进制或字符串比较。我相信二进制比较会更快。如果是的话,那么即使程序员现在每次必须对数据进行编码/解码,也bytea
可能比更好VARCHAR
。
我可能是错的,但我认为两者bytea
并VARCHAR
会(通过文字或文字),由字节比较(平等)字节。有没有一种方法可以“跳过”此逐步比较,而只是比较“整个过程”?(我不这么认为,但是不进行成本检查)。
我认为按原样存储bytea
是最好的解决方案,但是我想知道是否还有其他选择我会忽略。此外,我在解决方案1上表达的同样担忧仍然成立:比较开销是否足以让我担心?
“创意”解决方案
我想出了两个非常有效的“创意”解决方案,但我不确定在什么程度上使用(即,如果我无法将它们扩展到表中的几千行)。
3.储存为,UUID
但附有“标签”
不使用UUID的主要原因是,程序员可以更好地调试应用程序。但是,如果我们可以同时使用两者:数据库将所有键UUID
仅存储为s,但是在进行查询之前/之后包装对象。
例如,程序员要求ACC-{UUID}
,数据库将忽略ACC-
零件,获取结果,然后将所有结果返回为{domain}-{UUID}
。
对于某些带有存储过程或函数的黑客来说,这也许是可能的,但是我想到了一些问题:
- 这(在每个查询中删除/添加域)是否会产生大量开销?
- 这有可能吗?
我以前从未使用过存储过程或函数,因此不确定是否可能。有人可以照亮吗?如果我可以在程序员和存储的数据之间添加一个透明层,那似乎是一个完美的解决方案。
4.(我的最爱)存储为IPv6 cidr
是的,你没有看错。事实证明,IPv6地址格式完美解决了我的问题。
- 我可以在前几个八位位组中添加域和子域,并使用其余的作为随机字符串。
- 该碰撞几率都OK。(虽然我不会使用2 ^ 128,但仍然可以。)
- 平等比较(希望)得到了优化,所以我可能会比简单地使用获得更好的性能
bytea
。 - 实际上,我可以执行一些有趣的比较,例如
contains
,具体取决于域及其层次结构的表示方式。
例如,假设我使用代码0000
来表示域“产品”。密钥0000:0db8:85a3:0000:0000:8a2e:0370:7334
将代表产品0db8:85a3:0000:0000:8a2e:0370:7334
。
这里的主要问题是:与相比bytea
,使用cidr
数据类型有什么主要的优点或缺点?
varchar
许多其他问题。我不知道pg的领域,这对我很了解。我看到域用于验证给定查询是否使用了正确的对象,但是它仍然依赖于具有非整数索引。不知道这里是否有“安全”的使用方法serial
(没有一个锁定步骤)。
varchar
。考虑将其FK
integer
设为一种类型,并为其添加查找表。这样一来,您既可以拥有人类可读性,又可以保护合成组件PK
免受插入/更新异常(放入不存在的域)的影响。