我们必须在MySQL InnoDB数据库中摄取和存储1500亿条记录。特别是一个字段是VARCHAR的字段,占用了大量空间。其特点:
我尝试过以下方法:
答案 0 :(得分:1)
像MD5这样的哈希函数在32个十六进制字符的字符串中产生128位哈希值,但您可以使用UNHEX()将该值减半到16个二进制字符,并将结果存储在类型列中BINARY(16)。请参阅我对What data type to use for hashed password field and what length?
的回答MD5具有2个 128 不同的哈希值,或340,282,366,920,938,463,463,374,607,431,768,211,456。即使你有150亿个不同的输入,两个不同字符串导致碰撞的可能性相当低。请参阅How many random elements before MD5 produces collisions?如果您仍然担心,请使用SHA1或SHA2。
但是,我对你尝试使用哈希函数感到有些困惑。你不必关心原始字符串是什么,因为你必须明白散列是不可逆的。也就是说,您无法从哈希中获取原始字符串。我喜欢@Data Mechanics的答案,你应该在查询表中枚举唯一的字符串输入,并使用BIGINT主键(INT只有4亿个值,所以它不够大,不足150亿行)。
我明白你的意思是你必须查找字符串才能获得主键。您需要做的是编写自己的程序来执行此数据输入。您的计划将执行以下操作:
不幸的是,即使您在使用它作为HashMap的键之前MD5字符串,也需要超过1 TB的内存才能容纳150亿个条目的HashMap。
所以我建议将完整的映射集合放入数据库表中,并将其子集保存在内存中。所以你必须在上面做一个额外的步骤3.如果内存中的HashMap没有你的字符串的条目,首先检查数据库。如果它在数据库中,则将其加载到HashMap中。如果它不在数据库中,则继续将其插入数据库,然后插入HashMap。
您可能有兴趣使用像LruHashMap这样的类。它是一个具有最大大小的HashMap(您根据可以为其投入的内存量来选择)。如果你把一个新元素放满,它会踢出最近最少引用的元素。我在Apache Lucene中找到了这个实现,但也有其他实现。只是谷歌吧。
答案 1 :(得分:0)
varchar是普通文本吗?这是可压缩的3:1。 压缩只有一个字段可以将其降低到25-30个字节。然后使用VARBINARY(99)
。
INT
(4个字节)不够大用于规范150亿个不同的值,所以你需要更大的东西。 BIGINT
需要8个字节。 BINARY(5)
和DECIMAL(11,0)
各占5个字节,但处理起来比较麻烦。
但你关心的是标准化速度。我会更关注摄取速度,,特别是如果你需要索引这个列!
构建表需要多长时间?您还没有说出架构是什么;我猜你可以在InnoDB块中放置100行。我说你正在使用SSD并且可以获得10K IOP。 1.5B块/ 10K块/秒= 150K秒= 2天。假设除 ordered PRIMARY KEY
之外没有其他索引。 (如果没有订购,那么你将跳到桌面上,你将需要更多的IOP;将估计值改为6个月。)
列上的索引实际上将是一个150亿行'行' - 仅仅为索引BTree需要几TB。您可以在插入行时索引字段,也可以稍后构建索引。
那么,也许我们可以以类似的方式进行规范化?可是等等。你说这个专栏是如此之大,以至于你甚至无法装满桌子?那么我们必须压缩或标准化那个列?
如何完成加载?
LOAD DATA
来电(可能最好)?单行INSERTs
(更改" 2天"至" 2周"至少)?多行INSERTs
(100-1000好)?autocommit
?交易简短?一个巨大的交易(这是致命的)? (建议每COMMIT
行1K-10K行。)或者表格是 MyISAM 吗?磁盘占用空间将显着缩小。我的大多数评论仍然适用。
返回MD5 / SHA2。构建规范化表,假设它比可以缓存在RAM中的规模大得多,无论你怎么做,它都将是一个杀手。但是,让我们先了解其他一些细节。
另请参阅TokuDB(适用于较新版本的MariaDB),以获得良好的高速摄取和索引。正如我已经解释的那样,TokuDB会减慢某些的表大小,而InnoDB / MyISAM将减慢到抓取。 TokuDB也会自动压缩;有人说是10倍。我没有任何速度或空间估计,但我认为TokuDB非常有前途。
计划B
似乎真正的问题在于压缩或规范路由器地址'。回顾一下:在1500亿行中,大约有150亿个不同的值,加上NULLs
的一小部分。字符串平均为75个字节。由于字符串的性质,压缩可能无效。所以,让我们专注于规范化。
id必须至少为5个字节(处理15B个不同的值);字符串平均为75个字节。 (我假设这是字节,而不是字符。)为BTree等添加一些开销,总数大约在2TB左右。
我认为在加载表时路由器地址是相当随机的,因此查找“下一步”的地址。要插入的地址是在不断增长的索引BTree中的随机查找。一旦索引增长超过buffer_pool(小于768GB),I / O将越来越频繁地需要。在加载结束时,插入的4行中大约有3行必须等待以从该索引BTree读取以检查已存在的行。我们正在研究月的加载时间,即使使用SSD也是如此。
那么,可以做些什么呢?考虑以下。用MD5和UNHEX哈希地址 - 16个字节。把它放在表格中。同时写一个文件,其中包含md5的十六进制值,加上路由器地址 - 150B行(跳过NULL)。使用重复数据删除对文件进行排序。 (在md5上排序。)从排序文件(15B行)构建规范化表。
结果:负载相当快(但很复杂)。路由器地址不是75字节(也不是5字节),而是16.规范化表存在且有效。
答案 2 :(得分:-2)
你说它高度复制了吗? 我的第一个想法是创建另一个表,其中包含实际的varchar值和一个指向此值的主要int键。
然后现有的表可以简单地更改为包含对该键的引用(并且还可以有效地索引)。