复合索引的顺序在 MongoDB 性能方面有何影响?

Posted

技术标签:

【中文标题】复合索引的顺序在 MongoDB 性能方面有何影响?【英文标题】:How does the order of compound indexes matter in MongoDB performance-wise? 【发布时间】:2016-02-06 08:00:37 【问题描述】:

我们需要以与查询参数相同的顺序创建复合索引。这个顺序对性能有影响吗?

假设我们有一个地球上所有人类的集合,索引在 sex(99.9% 的时间是“男性”或“女性”,但仍然是字符串(不是二进制))和 name 的索引。

如果我们希望能够选择某个sex 和某个name 的所有人,例如所有名为 “John” 的“男性”,首先使用 sex 还是 name 的复合索引更好?为什么(不)?

【问题讨论】:

我不认为排序在性能方面很重要,但在重用方面 - 当您创建复合索引“name,sex”时,仅在查询“name”时可以重用索引(但是仅用于“sex”)-分别在创建复合索引“sex,name”时,仅在查询“sex”时可以重用该索引(但不能仅用于“name”)。 你需要对它们进行排序吗? @inspired 不是这些键 这真的取决于你的使用情况。 Mong 对于如何处理索引有很多选择。您可以定义复合索引或单个索引; Mongo 可以与其他索引相交以执行您的查询。还有其他一些概念,例如覆盖查询的索引,它们有一些限制。因此,这实际上取决于您要进行的每个特定查询及其预期的文档格式。您能否提供有关您的用例的更多详细信息? @MarkPieszak 这个问题不是另一个问题的骗局,“MongoDB 以某种方式连接复合键”也不是一个很好的答案,答案是正确的(对于普通复合键)索引的形成)但也不是 【参考方案1】:

请注意,多个相等谓词不必按照选择性从高到低的顺序排列。过去已经提供了该指南,但是由于 B-Tree 索引的性质以及 B-Tree 如何在叶页中存储所有字段值的组合,它是错误的。因此,无论键顺序如何,组合的数量都是完全相同的。

https://www.alexbevi.com/blog/2020/05/16/optimizing-mongodb-compound-indexes-the-equality-sort-range-esr-rule/

这篇博客文章不同意接受的答案。另一个答案中的基准也表明这并不重要。那篇文章的作者是“MongoDB 的高级技术服务工程师”,在这个话题上对我来说听起来像是一个值得信赖的人,所以我猜这个顺序真的不会影响平等领域的性能。我将遵循 ESR 规则。

还要考虑前缀。过滤 a: 1234 不适用于 b: 1, a: 1 的索引:https://docs.mongodb.com/manual/core/index-compound/#prefixes

【讨论】:

【参考方案2】:

雷德桑德罗,

您必须考虑 Index CardinalitySelectivity


1。索引基数

索引基数是指一个字段有多少个可能的值。 sex 字段只有两个可能的值。它具有非常低基数。其他字段如names, usernames, phone numbers, emails 等对于集合中的每个文档将具有更独特的值,这被认为是高基数

更大的基数

字段的基数越大,索引就越有用,因为索引缩小了搜索空间,使其成为一个更小的集合。

如果您在sex 上有一个索引,并且您正在寻找名叫约翰的男性。如果您首先按sex 编制索引,您只会将结果空间缩小大约 %50。相反,如果您按name 编制索引,您会立即将结果集缩小到一小部分名为 John 的用户,然后您将参考这些文档来检查性别。

经验法则

尝试在high-cardinality 键上创建索引,或将high-cardinality 键放在复合索引的首位。您可以在本书的复合索引部分了解更多信息:

MongoDB The Definitive Guide


2。选择性

此外,您希望使用索引selectively 并编写查询来限制具有索引字段的可能文档的数量。为简单起见,请考虑以下集合。如果您的索引是name:1,如果您运行查询 name: "John", sex: "male"。您必须扫描1 文档。因为您允许 MongoDB 具有选择性。

_id:ObjectId(),name:"John",sex:"male"
_id:ObjectId(),name:"Rich",sex:"male"
_id:ObjectId(),name:"Mose",sex:"male"
_id:ObjectId(),name:"Sami",sex:"male"
_id:ObjectId(),name:"Cari",sex:"female"
_id:ObjectId(),name:"Mary",sex:"female"

考虑以下集合。如果您的索引是sex:1,如果您运行查询sex: "male", name: "John"。您必须扫描4 文档。

_id:ObjectId(),name:"John",sex:"male"
_id:ObjectId(),name:"Rich",sex:"male"
_id:ObjectId(),name:"Mose",sex:"male"
_id:ObjectId(),name:"Sami",sex:"male"
_id:ObjectId(),name:"Cari",sex:"female"
_id:ObjectId(),name:"Mary",sex:"female"

想象一下更大数据集上可能存在的差异。


复合索引的一点解释

很容易对复合索引做出错误的假设。根据MongoDB docs on Compound Indexes。

MongoDB 支持复合索引,其中单个索引结构 在集合的文档中保存对多个字段的引用。 下图说明了一个复合索引的示例 两个字段:

当您创建复合索引时,1 个索引 将包含多个字段。因此,如果我们按"sex" : 1, "name" : 1 对集合进行索引,则索引大致如下:

["male","Rick"] -> 0x0c965148
["male","John"] -> 0x0c965149
["male","Sean"] -> 0x0cdf7859
["male","Bro"] ->> 0x0cdf7859
...
["female","Kate"] -> 0x0c965134
["female","Katy"] -> 0x0c965126
["female","Naji"] -> 0x0c965183
["female","Joan"] -> 0x0c965191
["female","Sara"] -> 0x0c965103

如果我们按"name" : 1, "sex" : 1 对集合进行索引,则索引大致如下:

["John","male"] -> 0x0c965148
["John","female"] -> 0x0c965149
["John","male"] -> 0x0cdf7859
["Rick","male"] -> 0x0cdf7859
...
["Kate","female"] -> 0x0c965134
["Katy","female"] -> 0x0c965126
["Naji","female"] -> 0x0c965183
["Joan","female"] -> 0x0c965191
["Sara","female"] -> 0x0c965103

name:1 用作Prefix 将在使用复合索引时为您提供更好的服务。关于这个主题还有更多可以阅读的内容,我希望这可以提供一些清晰的信息。

【讨论】:

你忘了提到一个非常重要的选择性 暂时赞成。我明白了这个理论,这是有道理的。这意味着虽然集合一次匹配一个字段的组合索引。 (与 field_a == index_a && field_b == index_b 相反,顺序无关紧要,我假设这是因为只循环一次集合是有意义的。)是否有来源可以验证这一点? @Redsandro 复合索引基本上是树,MongoDB 会向下遍历树,最简单的方法是执行基数 $ins blog.mongolab.com/2012/06/cardinal-ins 大多数数据库实现一棵树或另一棵树,但像 mysql 这样的技术倾向于容纳更大的树木,也可以上下左右移动等 @Redsandro 查看我在答案中添加的内容。 @AbdullahRasheed 我早就应该接受这个答案了。【参考方案3】:

我要说我自己对此做了一个实验,发现首先使用识别不佳的索引键似乎没有性能损失。 (我正在使用带有wiredtiger的mongodb 3.4,它可能与mmap不同)。我将 2.5 亿个文档插入到一个名为 items 的新集合中。每个文档如下所示:


    field1:"bob",
    field2:i + "",
    field3:i + ""

"field1" 始终等于"bob""field2" 等于 i,所以它是完全唯一的。首先我在 field2 上进行了搜索,花了一分钟多的时间扫描了 2.5 亿份文档。然后我像这样创建了一个索引:

`db.items.createIndex(field1:1,field2:1)`

当然,field1 在每个文档上都是“bob”,因此索引必须在找到所需文档之前搜索多个项目。然而,这不是我得到的结果。

索引创建完成后,我对集合进行了另一次搜索。这次我得到了下面列出的结果。您会看到 "totalKeysExamined" 每次都是 1。因此,也许有了有线老虎或其他东西,他们已经想出了如何更好地做到这一点。我已经阅读了wiredtiger实际上压缩了索引前缀,所以这可能与它有关。

db.items.find(field1:"bob",field2:"250888000").explain("executionStats")


    "executionSuccess" : true,
    "nReturned" : 1,
    "executionTimeMillis" : 4,
    "totalKeysExamined" : 1,
    "totalDocsExamined" : 1,
    "executionStages" : 
        "stage" : "FETCH",
        "nReturned" : 1,
        "executionTimeMillisEstimate" : 0,
        "works" : 2,
        "advanced" : 1,
        ...
        "docsExamined" : 1,
        "inputStage" : 
            "stage" : "IXSCAN",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            ...
            "indexName" : "field1_1_field2_1",
            "isMultiKey" : false,
            ...
            "indexBounds" : 
                "field1" : [
                    "[\"bob\", \"bob\"]"
                ],
                "field2" : [
                    "[\"250888000\", \"250888000\"]"
                ]
            ,
            "keysExamined" : 1,
            "seeks" : 1
        
    

然后我在 field3 上创建了一个索引(与字段 2 具有相同的值)。然后我搜索:

db.items.find(field3:"250888000");

与使用复合索引的时间相同的 4ms。我对 field2 和 field3 使用不同的值重复了很多次,每次都得到了微不足道的差异。这表明,使用wiredtiger,在索引的第一个字段上区分差,不会影响性能。

【讨论】:

keysExamined 在这里表示它查看的不同索引的数量——这并不意味着它正在查看的索引部分的数量。我认为与获取文档的总时间相比,两个索引顺序之间的任何差异都会非常小,所以如果我们想真正了解性能差异,我们需要运行一个负载测试脚本相当长的一段时间。 我不认为您的用例是低基数性能下降的好例子,因为最终复合键具有高基数。没错,要检索项目,引擎必须读取树的一个额外节点(“bob”),但您不会注意到这一点;无论如何,下一次读取的行为就像一个高基数索引。当您想在 2.5 亿人中找到一个名为“john mckenzy”、年龄为 34 岁但您的索引仅针对“年龄”的人时,问题就来了。引擎将在那里找到年龄 = 34 的 5M 记录,并且必须在该列表中查找该特定记录。这里索引没用。

以上是关于复合索引的顺序在 MongoDB 性能方面有何影响?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle:索引中的列顺序重要吗?

通用选择器对性能有何影响?

在 Rails 3 应用程序中使用 require_dependency 对性能有何影响?

在 web2py 中使用“rows.render()”对性能有何影响?

使用多个中间件对应用程序性能有何影响?或者多少中间件对应用程序性能有好处? [关闭]

在 32 位系统上使用 int64_t 而不是 int32_t 对性能有何影响?