持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
介绍
复合索引是多个字段组合而成的索引,可以支持在多个字段傻姑娘匹配的查询,其性质和单字段索引类似。但不同的是,复合索引中字段的顺序、字段的升降序对查询性能有直接的影响,因此在设计复合索引时则需要考虑不同的查询场景。
注意:
- 任意一个复合索引字段不能超过32个
MongoDB 4.2之前复合索引不包含哈希索引,MongoDB 4.4开始复合索引可能包含单个哈希索引字段
创建方式:
db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )
结构
复合索引的结构依然是基于B+树的结构,我们可能会认为索引是二维的,但事实并非如此。实际上是一维的,可以看作是一个有序列表。
例如:我们对last_name和first_name来进行索引
如果我们想找到
Adam Bailey这条记录,则通过Bailey和Adam索引键,然后指向我们需要的记录。我们扫描到这个索引,则就会返回指向的文档。
如果我们想找到Chris Bailey,则先去找last_name = Bailey的,然后再找出first_name = Chris的,最后找到该条文档。如果我们想跟据last_name先找那将非常容易。
但是,我们如果想要先找first_name = James,我们必须遍历每一个键或者每一个文档才能找到。
正如我们所看到的,首先出现的一个或多个字段在某些方面比后面的字段更重要。
应用
继续使用 person.json的数据。
我们先查询以下语句
exp = db.people.explain("executionStats")
exp.find( { "last_name" : "Frazier", "first_name": "Jasmine" } )
{
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 27,
totalKeysExamined: 0,
totalDocsExamined: 50474,
executionStages: {
stage: 'COLLSCAN',
filter: {
'$and': [
{ first_name: { '$eq': 'Jasmine' } },
{ last_name: { '$eq': 'Frazier' } }
]
},
}
}
我们可以看到找到这条文档花费了27ms,使用了全表扫描,意味着我们没有使用索引。我们必须通过扫描5万条记录才能查询到一条记录。
然后我们给last_name建立一个单键索引,然后再次查询
db.people.createIndex({"last_name": 1})
exp.find( { "last_name" : "Frazier", "first_name": "Jasmine" } )
{
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 2,
totalKeysExamined: 31,
totalDocsExamined: 31,
executionStages: {
stage: 'FETCH',
filter: { first_name: { '$eq': 'Jasmine' } }
inputStage: {
stage: 'IXSCAN'
}
}
}
我们可以看到扫描了31个索引键并找到一条文档,相比较全表要快很多。查询时间只用了2ms,然后再通过FETCH扫描31条文档才找到first_name = Jasmine这条记录。
假如说我们将first_name做成复合索引,应该是什么情况?要注意字段之间的顺序以及排序是很重要的。
db.people.createIndex({"last_name": 1, "first_name": 1})
exp.find( { "last_name" : "Frazier", "first_name": "Jasmine" } )
{
explainVersion: '1',
queryPlanner: {
rejectedPlans: [
{
stage: 'FETCH',
filter: { first_name: { '$eq': 'Jasmine' } },
inputStage: {
stage: 'IXSCAN',
keyPattern: { last_name: 1 },
indexName: 'last_name_1',
}
}
]
},
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 1,
totalKeysExamined: 1,
totalDocsExamined: 1,
executionStages: {
stage: 'FETCH',
nReturned: 1,
inputStage: {
stage: 'IXSCAN',
nReturned: 1,
keyPattern: { last_name: 1, first_name: 1 },
indexName: 'last_name_1_first_name_1'
}
}
}
}
这次我们可以看到只花费了1毫秒,只扫描了一个索引键,还扫描了一个文档,可以说是非常快,比例也是最佳的比例。我们还可以看到将我们前面的单键索引,服务器给拒绝了。
让我们在稍微修改一下查询
db.people.createIndex({"last_name": 1, "first_name": 1})
exp.find( { "last_name" : "Frazier", "first_name": {$gte: "L"} } )
{
executionStats: {
executionSuccess: true,
nReturned: 16,
executionTimeMillis: 0,
totalKeysExamined: 16,
totalDocsExamined: 16,
}
}
我们可以发现扫描了16个索引键,16个文档,返回了16个文档,这个比例依然是最佳的。还不必去查看其他多余的文档。因此,我们可以通过复合索引查询多字段。并且会按照我们索引键的排序方式给我们返回。