在150B行表中存储VARCHAR的最有效方法

时间:2016-07-09 14:08:37

标签: mysql hash md5 innodb

我们必须在MySQL InnoDB数据库中摄取和存储1500亿条记录。特别是一个字段是VARCHAR的字段,占用了大量空间。其特点:

  • 可以为NULL
  • 高度重复但我们无法重复删除,因为它会以指数方式增加摄取时间
  • 平均长度约为75个字符
  • 它必须有一个索引,因为它必须与另一个表
  • 连接
  • 我们不需要以人类可读的格式存储它,但我们需要能够将其与另一个表格相匹配,而该表格必须具有此列的相同格式

我尝试过以下方法:

  • 压缩表格,这有助于节省空间,但会大大增加摄取时间,所以我不确定压缩对我们有用
  • 尝试对SHA2进行哈希处理,将字符串长度减少到56,这样可以节省合理的空间,但还不够。此外,我不确定SHA2是否会为此类数据生成唯一值
  • 考虑MD5会进一步将字符串长度减少到可能正确的级别,但不能再次确定MD5是否足以生成唯一值以便能够与另一个表匹配

3 个答案:

答案 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亿行)。

我明白你的意思是你必须查找字符串才能获得主键。您需要做的是编写自己的程序来执行此数据输入。您的计划将执行以下操作:

  1. 创建内存中的哈希表,以将字符串映射到整数主键。
  2. 阅读输入行
  3. 如果哈希表还没有输入条目,请将该字符串插入查找表并获取生成的插入ID。将其存储为哈希表中的新条目,将字符串作为键,将插入ID作为该条目的值。
  4. 否则哈希表确实有一个条目,只需从哈希表中读取主键bigint。
  5. 将bigint作为外键插入真实数据表,以及要加载的其他数据。
  6. 循环到第2步。
  7. 不幸的是,即使您在使用它作为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。您可以在插入行时索引字段,也可以稍后构建索引。

  • 插入时构建索引,即使有了InnoDB"更改缓冲区"的优势,最终也会减慢到比每行1次磁盘更快的速度快插入。你在使用固态硬盘吗? (旋转驱动器的额定值约为10毫秒/击中。)假设您每秒可以获得10K命中(插入)。这可以达到15M秒,即6个月。
  • 在加载整个表之后构建索引 ...这有效地构建了一个包含1500亿行的文件,对其进行排序,然后按顺序构建索引。这可能需要一周而不是几个月。但是......在索引构建过程中,它需要足够的磁盘空间来存储表的第二个副本(可能更多)。

那么,也许我们可以以类似的方式进行规范化?可是等等。你说这个专栏是如此之大,以至于你甚至无法装满桌子?那么我们必须压缩或标准化那个列?

如何完成加载?

  • 多个LOAD DATA来电(可能最好)?单行INSERTs(更改" 2天"至" 2周"至少)?多行INSERTs(100-1000好)?
  • autocommit?交易简短?一个巨大的交易(这是致命的)? (建议每COMMIT行1K-10K行。)
  • 单线程(也许不够快)?多线程(其他问题)?
  • My discussion of high-speed-ingestion

或者表格是 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键。

然后现有的表可以简单地更改为包含对该键的引用(并且还可以有效地索引)。