我们需要以与查询参数相同的顺序创建复合索引。这个订单在性能方面是否重要?
想象一下,我们拥有地球上所有人类的集合,其中sex
的索引(99.9%的时间是“男性”或“女性”,但字符串(不是二元))和{{{ 1}}。
如果我们希望能够选择具有特定name
的特定sex
的所有人,例如所有名为“John”的“男性”,最好是首先使用name
或首先sex
的复合索引?为什么(不是)?
答案 0 :(得分:51)
Redsandro,
您必须考虑 Index Cardinality
和 Selectivity
。
索引基数是指字段有多少可能的值。字段sex
只有两个可能的值。它具有非常低基数。其他字段(例如names, usernames, phone numbers, emails
等)对于集合中的每个文档都具有更独特的值,这被视为高基数。
字段的基数越大,索引就越有用,因为索引会缩小搜索空间,使其变得更小。
如果您在sex
上有索引,并且您正在寻找名为John的男士。如果您首先将sex
编入索引,则只会将结果空间缩小约50%。相反,如果您使用name
建立索引,则会立即将结果集缩小到名为John的用户的一小部分,然后您将引用这些文档来检查性别。
尝试在high-cardinality
键上创建索引或首先将high-cardinality
键放在复合索引中。您可以在本书的复合索引部分中阅读更多相关内容:
<强> MongoDB The Definitive Guide 强>
此外,您希望使用索引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"}
想象一下更大的数据集可能存在差异。
很容易对复合索引做出错误的假设。根据{{3}}。
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
答案 1 :(得分:1)
我要说我自己做了一个实验,发现首先使用差别很差的索引键似乎没有性能损失。 (我正在使用带有wiretiger的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。所以也许有线老虎或他们已经想出如何做得更好。我已经阅读过wiretiger实际上会压缩索引前缀,因此可能与它有关。
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({字段3: “250888000”});
与复合指数相同的4ms。我用field2和field3的不同值重复了这么多次,并且每次都有微不足道的差异。这表明,对于wiretiger,在索引的第一个字段上具有差的差异没有性能损失。
答案 2 :(得分:1)
请注意,多个相等谓词不必按选择性从高到低的顺序排列。过去已经提供了该指南,但是由于 B 树索引的性质以及在叶页中 B 树如何存储所有字段值的组合,它是错误的。因此,无论键顺序如何,组合的数量都完全相同。
这篇博客文章不同意接受的答案。另一个答案中的基准也表明这无关紧要。那篇文章的作者是“MongoDB 的高级技术服务工程师”,这对我来说在这个主题上听起来像是一个值得信赖的人,所以我猜这个顺序毕竟不会影响相等字段的性能。我将改为遵循 ESR 规则。