持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
介绍
单键索引时MongoDB必须提供的最简单的索引,它是后续所有索引的基础。
在某一个特定的字段建立的索引,主要具有以下特征:
- 只有一个字段
- 可以精确查找某一个值。例:按照id查询
- 可以范围查找。例:按照时间范围查询
- 可以使用点符号(
.)来索引内嵌文档。例如:内嵌文档的某一个字段也是可以创建索引的 - 可以通过单个查询找到不同的值。例如:
$in操作
创建单键索引命令
db.<collection>.createIndex({ <field>: <direction>})
应用
导入数据
首先,我们先在mongo中导入一个 person.json文件
mongoimport -h 127.0.0.1:27017 -u admin -p admin --authenticationDatabase=admin -d test -c people people.json
connected to: mongodb://127.0.0.1:27017/
50474 document(s) imported successfully. 0 document(s) failed to import.
我们一共导入了5万多条记录。我们打开shell执行以下查询命令,并加上explain。
db.people.find({ "ssn" : "720-38-5636" }).explain("executionStats")
{
explainVersion: '1',
queryPlanner: {
namespace: 'test.people',
indexFilterSet: false,
parsedQuery: { ssn: { '$eq': '720-38-5636' } },
queryHash: 'EFBD2442',
planCacheKey: 'EFBD2442',
maxIndexedOrSolutionsReached: false,
maxIndexedAndSolutionsReached: false,
maxScansToExplodeReached: false,
winningPlan: {
//全表扫描
stage: 'COLLSCAN',
//筛选条件
filter: { ssn: { '$eq': '720-38-5636' } },
direction: 'forward'
},
rejectedPlans: []
},
executionStats: {
//执行成功
executionSuccess: true,
//实际返回条数
nReturned: 1,
//耗费时间:毫秒
executionTimeMillis: 31,
//扫描的索引key数
totalKeysExamined: 0,
//扫描的文档数
totalDocsExamined: 50474,
executionStages: {
stage: 'COLLSCAN',
filter: { ssn: { '$eq': '720-38-5636' } },
nReturned: 1,
executionTimeMillisEstimate: 3,
works: 50476,
advanced: 1,
needTime: 50474,
needYield: 0,
saveState: 50,
restoreState: 50,
isEOF: 1,
direction: 'forward',
docsExamined: 50474
}
},
command: { find: 'people', filter: { ssn: '720-38-5636' }, '$db': 'test' },
serverInfo: {
host: '65fac2418820',
port: 27017,
version: '6.0.1',
gitVersion: '32f0f9c88dc44a2c8073a5bd47cf779d4bfdee6b'
},
serverParameters: {
internalQueryFacetBufferSizeBytes: 104857600,
internalQueryFacetMaxOutputDocSizeBytes: 104857600,
internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
internalDocumentSourceGroupMaxMemoryBytes: 104857600,
internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
internalQueryProhibitBlockingMergeOnMongoS: 0,
internalQueryMaxAddToSetBytes: 104857600,
internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600
},
ok: 1
}
通过queryPlanner.winningPlan 我们可以查看出我们执行全表扫描(COLLSCAN),这说明我们执行的过程中扫描了每个document
从executionStats可以看到扫描了5万多条,因为我们的集合就有5万条,最终只返回了一条。这样效率非常低下,当随着数据量的增多,我们要扫描的数据会越来越多。
添加索引
现在我们在ssn字段上添加一个升序索引
db.people.createIndex( { ssn : 1 } )
通过这样做,mongodb 必须查看集合中每一个文档,拿出ssn字段,如果文档中不存在该字段,则该键是一个空值,我们再次执行查询语句。
//创建一个people集合的explain对象,后续可以重复使用
exp = db.people.explain("executionStats")
exp.find( { "ssn" : "720-38-5636" } )
{
queryPlanner: {
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
}
}
},
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 3,
totalKeysExamined: 1,
totalDocsExamined: 1,
executionStages: {
stage: 'FETCH',
nReturned: 1,
executionTimeMillisEstimate: 0,
works: 2,
advanced: 1,
inputStage: {
stage: 'IXSCAN',
nReturned: 1,
executionTimeMillisEstimate: 0,
works: 2,
}
}
}
}
这次我们再查看就会发现是索引扫描(IXSCAN),只扫描了一个索引,就返回了一条记录,而不是5万条记录。
这种情况下,我们只需要一个索引键,然后该键指向了我们需要的文档。效率比全表扫描高了很多。
如果查询的字段不是索引键?
现在我们不利用索引键来查询
exp.find( { last_name : "Acevedo" } )
{
queryPlanner: {
winningPlan: {
stage: 'COLLSCAN'
},
},
executionStats: {
executionSuccess: true,
nReturned: 10,
executionTimeMillis: 28,
totalKeysExamined: 0,
totalDocsExamined: 50474,
}
}
如果我们不使用索引的字段来查询,是不会使用索引的。所以还是全表扫描
内嵌文档索引
现在我们插入2条数据包含内嵌文档的数据。
db.examples.insertOne( { _id : 0, subdoc : { indexedField: "value", otherField : "value" } } )
db.examples.insertOne( { _id : 1, subdoc : { indexedField : "wrongValue", otherField : "value" } } )
现在内嵌文档的indexedField加上索引
db.examples.createIndex( { "subdoc.indexedField" : 1 } )
执行查询
db.examples.explain("executionStats").find( { "subdoc.indexedField" : "value" } )
{
queryPlanner: {
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN'
}
}
},
executionStats: {
executionSuccess: true,
nReturned: 1,
executionTimeMillis: 1,
totalKeysExamined: 1,
totalDocsExamined: 1,
executionStages: {
stage: 'FETCH',
nReturned: 1,
inputStage: {
stage: 'IXSCAN',
}
}
}
}
注意:
永远不应该在内嵌文档的字段上建立索引,因为这样做必须查询整个内嵌文档。