MD5字段的最佳数据类型是什么?


35

我们正在设计一个读取量很大的系统(每分钟读取数万次)。

  • 有一个表names,可以作为一种中央注册表。每行都有一个text字段representation和一个唯一字段,该字段key是该字段的MD5哈希值representation1该表当前具有数千万条记录,并且预计在应用程序的生命周期内将增长到数十亿条。
  • 还有许多其他表(具有高度变化的模式和记录计数)引用该names表。这些表之一中的任何给定记录都保证有一个name_key,从功能上讲,该names表是该表的外键。

1:顺便说一句,正如您所料,此表中的记录一旦写入便是不可变的。

对于除表以外的任何给定表names,最常见的查询将遵循以下模式:

SELECT list, of, fields 
FROM table 
WHERE name_key IN (md5a, md5b, md5c...);

我想针对读取性能进行优化。我怀疑我的第一站应该是最小化索引的大小(尽管我不介意在那里被证明是错误的)。

问题:和列
的最佳数据类型是什么? 有没有理由使用过?还是?keyname_key
hex(32)bit(128)BTREEGIN

Answers:


41

数据类型uuid非常适合的任务。varchartext表示,它仅占用16个字节,而RAM中仅占用37个字节。(或者磁盘上有33个字节,但是在许多情况下,奇数需要填充以有效地使其变为40个字节。)这种uuid类型还有更多优势。

例:

SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash

详细信息和更多说明:

如果不需要md5的加密组件,则可以考虑使用其他(更便宜的)哈希函数,但是对于您的用例,我将使用md5(大多数情况下是只读的)。

一句警告:对于你的情况(immutable once written)一个函数依赖(伪自然)PK是好的。但是如果有可能进行更新,同样会很痛苦text。考虑纠正一个错字:PK和所有相关索引,FK列dozens of other tables以及其他引用也必须更改。表和索引膨胀,锁定问题,更新缓慢,引用丢失,...

如果text可以在正常操作中进行更改,则替代PK将是更好的选择。我建议使用一bigserial列(范围为-9223372036854775808 to +9223372036854775807- 九十二点三十二分之三千万分之三百七十二亿兆三十六亿分之三十亿左右的东西)作为的不同值billions of rows。在任何情况下都可能是一个好主意:数十个FK列和索引使用8 字节而不是16个字节!)。或随机UUID更大的基数或分布式系统。您总是可以另外存储md5(as uuid),以便快速从原始文本中查找主表中的行。有关:

至于您的查询


解决@Daniel的评论:如果您希望不使用连字符的表示形式,请删除要显示的连字符:

SELECT replace('90b7525e-84f6-4850-c2ef-b407fae3f271', '-', '')

但我不会打扰。默认表示就可以了。问题实际上不是这里的代表。

如果其他各方应该采用不同的方法,并在字符串中添加不带连字符的字符串,那么这也不是问题。Postgres接受几种合理的文本表示形式作为的输入uuid文档

PostgreSQL还接受以下替代形式输入:使用大写数字,用大括号括起来的标准格式,省略一些或所有连字符,在任何四位数组之后添加连字符。例如:

A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
a0eebc999c0b4ef8bb6d6bb9bd380a11
a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}

更重要的是,该md5()函数返回text,你会用decode()转换为bytea和的默认表示是:

SELECT decode(md5('Store hash for long string, maybe for index?'), 'hex')

\220\267R^\204\366HP\302\357\264\007\372\343\362q

您将不得不encode()再次获得原始文本表示形式:

SELECT encode(my_md5_as_bytea, 'hex');

最重要的是,由于内部开销,存储为的值bytea将在RAM中占据20个字节(在磁盘上占17个字节,带有padding占24个字节),这特别不利于简单索引的大小和性能。varlena

一切都有利于uuid这里。


1
这是“ uuid”的合法名称吗?如果我太学究了,请原谅,但是我认为我看到的是“ uuid”数据类型是针对以二进制格式存储长度为16个八位位组的数字。但是术语“ uuid”表示一种特殊的生成/散列算法以及以5个用破折号分隔的十六进制字符组成的常规文本表示形式。如果此类型名称强烈暗示了UUID / GUID的生成,那么至少对于程序员而言,使用此类型存储哈希值是否有点误导?
安德鲁·沃尔夫

2
@AndrewWolfe:完全合法,IMO。不要被名字迷住了。它是一个16字节的实体,具有一组方便的提供的类型转换和输入/输出逻辑。实际情况甚至需要“唯一标识符”。您也可以将各种字符数据存储在text列中-即使它根本不是“文本”。
Erwin Brandstetter,2016年

如果将MD5哈希值转换为基数64,该如何存储呢
PirateApp

2
@PirateApp,请先对其进行解码:SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;
纽约

1
@nyov:uuid是一种16字节类型,不能存储任何产生160至512位之间的SHA算法的结果。没有类似的类型适合于Postgres的标准发行版。您可以创建一个...失败,默认为bytea-像pg_crypto一样。
Erwin Brandstetter

2

我会将MD5存储在textvarchar列中。各种字符数据类型之间没有性能差异。您可能希望通过使用varchar(xxx)来确保md5值从不超过特定长度来限制md5值的长度。

大型IN列表通常并不是真的很快,因此最好执行以下操作:

with md5vals (md5) as (
  values ('one'), ('two'), ('three')
)
select t.*
from the_table t
  join md5vals m on t.name_key  = m.md5;

有时被称为更快的另一个选择是使用数组:

select t.*
from the_table t
where name_key = ANY (array['one', 'two', 'three']);

当您只是在比较相等性时,常规的BTree索引应该可以。这两个查询都应该能够使用这样的索引(尤其是如果仅选择行的一小部分的话)。


是否有不使用bit(128)或hex(32)的特殊原因?保证值可以整齐地适合这样的字段,我想防止分配错误的值。
bobocopy 2015年

3
@bobocopy:Postgres中没有“十六进制”数据类型。我从未使用过该bit类型,因此无法对此发表评论。给定预期的行数,Erwin的建议似乎更好,因为通过将其存储为UUID可以节省空间
a_horse_with_no_name 2015年

-1

另一种选择是使用4个INTEGER或2个BIGINT列。


2
就存储大小而言,这两个选项当然都适用,但是使用起来有多方便?也许您可以扩展答案以显示示例或以其他方式进行解释。
Andriy M
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.