哈希和升序索引之间的 Mongodb 性能差异(有啥理由不在无序字段中使用哈希?)

Posted

技术标签:

【中文标题】哈希和升序索引之间的 Mongodb 性能差异(有啥理由不在无序字段中使用哈希?)【英文标题】:Mongodb performance difference between Hash and Ascending indices (Any reason not to use hash in a not ordered field?)哈希和升序索引之间的 Mongodb 性能差异(有什么理由不在无序字段中使用哈希?) 【发布时间】:2015-04-04 11:51:15 【问题描述】:

在 mongodb 中有多种类型的index。对于这个问题,我对可用于排序的ascending (or descending) index 和根据文档“主要与分片集群一起使用以支持散列分片键”(source) 确保“更多数据分布均匀"(source)

我知道你不能创建像:db.test.ensureIndex( "key": "hashed", "sortOrder": 1 ) 这样的索引,因为你得到一个错误


    "createdCollectionAutomatically" : true,
    "numIndexesBefore" : 1,
    "errmsg" : "exception: Currently only single field hashed index supported.",
    "code" : 16763,
    "ok" : 0

我的问题:

索引之间:

    db.test.ensureIndex( "key": 1 )

    db.test.ensureIndex( "key": "hashed" )

对于查询db.products.find( key: "a" ),哪个性能更高?是hashedO(1)


我是如何回答这个问题的:

在我知道hashed 不能有多键索引之前,我创建了一个db.test.ensureIndex( "key": 1, "sortOrder": 1 ) 形式的索引,在创建它时我想知道哈希索引是否比升序索引(哈希通常是O(1))。我留下了现在的钥匙,因为(如上所述)db.test.ensureIndex( "key": "hashed", "sortOrder": 1 ) 是不允许的。但问题是哈希索引更快地通过键搜索仍然留在我的脑海中。

我做索引的情况是:

我有一个集合,其中包含按键分类的排序文档列表。

例如 key: a, sortOrder: 1, ..., key: a, sortOrder: 2, ..., key: a, sortOrder: 3, ..., key: b, sortOrder: 1, ..., key: b, sortOrder: 2, ..., ...

由于我使用key 进行分类并使用 sortOrder 进行分页,因此我总是使用key 的一个值来查询过滤,并使用sortOrder 来获取文档的顺序。

这意味着我有两个可能的查询:

第一页db.products.find( key: "a" ).limit(10).sort("sortOrder", 1) 对于其他页面db.products.find( key: "a" , sortOrder: $gt: 10 ).limit(10).sort("sortOrder", 1)

在这种特定情况下,使用 O(1) 搜索键和 O(log(n)) 搜索 sortOrder 是理想的,但这是不允许的。

【问题讨论】:

仔细考虑这一点,我不确定键中的哈希是否真的比二叉树快。我这么说是因为 log2(20.000.000) ~= 25 并且我不知道一个好的散列函数是否会比检查少于 30 个指针要快得多。 (在我的情况下,我不会超过 20MM 键) 如果您的应用需要经常插入和删除,那么哈希索引可能是最好的 我相信,如果我错了,我会检查并更新,哈希索引是伪装的 Btree 索引。 Btree 键是散列而不是字段值。因此,没有O(1)O(log n) 散列索引的渐近性能胜利,因为它们实际上是存储散列的Btree。 MongoDB 中散列索引的要点是均匀分布键值,这样当_id 上的散列索引用作分片键时,您可以在分片之间均匀分布写入。 @Robertiano 插入并不常见,最常见的操作是我发布的两个查询。 @wdberkeley 我知道哈希索引的实现可能是这样的。我在(hash usually is O(1)) 中写“通常”的原因正是如此。如果您错了,请告诉我。 【参考方案1】:

在特定的使用类型中,索引会更小!

是的!在满足以下所有三个条件的非常特定的场景中。

您的访问模式(您的搜索方式)必须仅查找具有特定索引字段值的文档(键值查找,例如,按 SKU 查找产品,或按 ID 查找用户等。 ) 您不需要对索引字段进行基于范围的查询或排序。 您的字段是一个非常大的字符串,并且 Mongo 的字段数字哈希值小于原始字段。

例如,我创建了两个索引,对于散列版本,索引的大小更小。这可以提高内存和磁盘利用率。

// The type of data in the collection. Each document is a random string with 65 characters.

  "myLargeRandomString": "40a9da87c3e22fe5c47392b0209f296529c01cea3fa35dc3ba2f3d04f1613f8e"

索引大约是普通版的1/4!

mongos> use MyDb
mongos> db.myCollection.stats()["indexSizes"]

    // A regular index. This one is sorted by the value of myLargeRandomString
    "myLargeRandomString_-1"     : 23074062336,

    // The hashed version of the index for the same field. It is around 1/4 of the original size.
    "myLargeRandomString_hashed" : 6557511680,

注意:

如果您已经使用_id 作为文档的外键,那么这无关紧要,因为默认情况下集合将具有_id 索引。 与往常一样,对您的数据进行自己的测试,以检查此更改是否真的会使您受益。在此类索引的搜索功能方面存在重大权衡。

【讨论】:

【参考方案2】:

对于查询db.products.find( key: "a" ),哪个性能更好?

鉴于key 字段在这两种情况下都被编入索引,复杂性索引搜索本身会非常相似。因为a 的值将是hashed,并存储在索引树中。

如果我们正在寻找总体性能成本,散列版本会在匹配索引树中的值之前对 a 的值进行散列处理的额外(可忽略不计)成本。另见mongo/db/index/hash_access_method.h

此外,散列索引将无法使用index prefix compression (WiredTiger)。索引前缀压缩对于某些数据集特别有效,例如具有低基数的数据集(例如,国家/地区),或具有重复值的数据集,例如电话号码、社会保障代码和地理坐标。它对compound indexes 尤其有效,其中第一个字段与第二个字段的所有唯一值重复。

有什么理由不在无序字段中使用哈希?

通常没有理由散列一个非范围值。要选择分片键,请考虑值的cardinality、frequency 和rate of change。

散列索引通常用于sharding 的特定情况。当shard key 值是monotonically increasing/decreasing 值时,数据的分布可能只进入一个分片。这是散列分片键能够改善写入分布的地方。极大地改进分片集群是一个小小的权衡。另见Hashed vs Ranged Sharding。

是否值得在文档中插入随机散列或值,并将其用于分片而不是在 _id 上生成的散列?

是否值得,取决于用例。自定义哈希值意味着对哈希值的任何查询都必须通过自定义哈希代码,即应用程序。

使用内置散列函数的优点是 MongoDB 在使用散列索引解析查询时会自动计算散列。因此,应用程序不需要计算散列。

【讨论】:

以上是关于哈希和升序索引之间的 Mongodb 性能差异(有啥理由不在无序字段中使用哈希?)的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB性能优化指南

MongoDB性能优化指南

如何定义复合和哈希 mongodb 索引?

MongoDB 哈希索引

数组、堆栈和队列之间的性能差异

MongoDB - _id 索引大小的奇怪差异