我们需要以与查询参数相同的顺序创建复合索引。这个顺序对性能有影响吗?
想象一下,我们有一个地球上所有人类的集合,其索引为sex
(99.9% 的时间为“男性”或“女性”,但仍然是字符串(不是二进制))和一个索引为name
.
如果我们希望能够选择某个特定sex
的name
所有人,例如所有名为“John”sex
的“男性”,那么使用first 还是first的复合索引更好name
?为什么不)?
我们需要以与查询参数相同的顺序创建复合索引。这个顺序对性能有影响吗?
想象一下,我们有一个地球上所有人类的集合,其索引为sex
(99.9% 的时间为“男性”或“女性”,但仍然是字符串(不是二进制))和一个索引为name
.
如果我们希望能够选择某个特定sex
的name
所有人,例如所有名为“John”sex
的“男性”,那么使用first 还是first的复合索引更好name
?为什么不)?
雷桑德罗,
你必须考虑Index Cardinality
和Selectivity
。
索引基数是指一个字段有多少可能的值。该字段sex
只有两个可能的值。它的基数很低。其他字段,如names, usernames, phone numbers, emails
等,对于集合中的每个文档都会有一个更独特的值,这被认为是高基数。
字段的基数越大,索引就越有用,因为索引缩小了搜索空间,使其成为一个更小的集合。
如果你有一个索引sex
并且你正在寻找名叫约翰的男人。如果您首先索引,您只会将结果空间缩小大约 %50 sex
。相反,如果您按 索引name
,您会立即将结果集缩小到一小部分名为 John 的用户,然后您将参考这些文档来检查性别。
尝试在high-cardinality
键上创建索引或将high-cardinality
键放在复合索引中。您可以在本书的复合索引部分中阅读更多相关信息:
此外,您希望有选择地使用索引并编写查询来限制具有索引字段的可能文档的数量。为简单起见,请考虑以下集合。如果你的索引是{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"}
想象一下更大数据集上可能存在的差异。
很容易对复合指数做出错误的假设。根据关于 Compound Indexes 的 MongoDB 文档。
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}
作为前缀将在使用复合索引时为您提供更好的服务。关于这个主题还有很多可以阅读的内容,我希望这可以提供一些清晰的信息。
请注意,多个相等谓词不必按照选择性从高到低的顺序排列。过去已提供此指南,但由于 B-Tree 索引的性质以及 B-Tree 如何在叶页中存储所有字段值的组合,它是错误的。因此,无论键顺序如何,组合的数量都完全相同。
这篇博客文章不同意接受的答案。另一个答案中的基准也表明这并不重要。那篇文章的作者是“MongoDB 的高级技术服务工程师”,在这个话题上对我来说听起来像是一个值得信赖的人,所以我猜这个顺序真的不会影响平等领域的性能。我将遵循 ESR 规则。
还要考虑前缀。过滤{ a: 1234 }
不适用于以下索引{ b: 1, a: 1 }
:https ://docs.mongodb.com/manual/core/index-compound/#prefixes
我要说我自己对此做了一个实验,发现首先使用区分度低的索引键似乎没有性能损失。(我正在使用带有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,在索引的第一个字段上存在差的差异不会导致性能损失。