常用命令
查看表的索引
db.<table>.getIndexes()
查看表索引的大小
db.<table>.totalIndexSize()
重建索引
db.<table>.reIndex()
删除索引
db.COLLECTION_NAME.dropIndex("INDEX-NAME")
db.COLLECTION_NAME.dropIndexes()
_id 索引无法删除。
执行计划
参考:MongoDB干货系列2-MongoDB执行计划分析详解(1) | MongoDB中文社区、MongoDB - 执行计划 - 听雨危楼 - 博客园
-- 千万测试数据
for(var i=1;i<10000000;i++){ db.indexDemo.insert({_id:i , num:'index:'+i ,address:'address:i%9999'})}
默认的查询计划 queryPlanner
-- 不使用索引
db.indexDemo.find({num:'index:99999'}).explain()db.indexDemo.createIndex( { num: 1 } )
db.indexDemo.getIndexes()
db.indexDemo.dropIndex("num_1")
{"explainVersion" : "1","queryPlanner" : {"namespace" : "study.goods", 【查询的表】"indexFilterSet" : false, 【是否有indexfilter】"parsedQuery" : { 【查询过滤条件】"qty" : {"$gt" : 50}},"queryHash" : "3DC2392F","planCacheKey" : "B7F8CFFA","maxIndexedOrSolutionsReached" : false,"maxIndexedAndSolutionsReached" : false,"maxScansToExplodeReached" : false,"winningPlan" : { 【最优执行计划详细内容】"stage" : "FETCH", 【FETCH:通过返回的index扫描。COLLSCAN:全表扫描】"inputStage" : { 【子stage】"stage" : "IXSCAN", 【表示进行索引扫描】"keyPattern" : { 【扫描的索引内容】"qty" : 1},"indexName" : "qty_1", 【使用的索引名称】"isMultiKey" : false, 【是否多列索引。索引建立在array上时是true】"multiKeyPaths" : {"qty" : [ ]},"isUnique" : false,"isSparse" : false,"isPartial" : false,"indexVersion" : 2,"direction" : "forward", 【查询的顺序:forward升序、backward降序】"indexBounds" : { 【索引扫描范围,没有制定范围就是[MaxKey,MinKey]】"qty" : ["(50.0, inf.0]"]}}},"rejectedPlans" : [ ] 【其它非最优的执行计划】},"command" : {"find" : "goods","filter" : {"qty" : {"$gt" : 50}},"sort" : {"qty" : 1},"$db" : "study"},"serverInfo" : { 【服务器信息】"host" : "fe9b0d04fcbd","port" : 27017,"version" : "5.0.5","gitVersion" : "d65fd89df3fc039b5c55933c0f71d647a54510ae"},"serverParameters" : {"internalQueryFacetBufferSizeBytes" : 104857600,"internalQueryFacetMaxOutputDocSizeBytes" : 104857600,"internalLookupStageIntermediateDocumentMaxSizeBytes" : 104857600,"internalDocumentSourceGroupMaxMemoryBytes" : 104857600,"internalQueryMaxBlockingSortMemoryUsageBytes" : 104857600,"internalQueryProhibitBlockingMergeOnMongoS" : 0,"internalQueryMaxAddToSetBytes" : 104857600,"internalDocumentSourceSetWindowFieldsMaxMemoryBytes" : 104857600},"ok" : 1
}
附加执行状态 executionStats
-- 在默认的执行计划信息上,多了 executionStats 信息
db.indexDemo.find({num:'index:99999'}).explain("executionStats")
{"explainVersion" : "1","queryPlanner" : {...},"executionStats" : {"executionSuccess" : true, 【是否执行成功】"nReturned" : 1, 【匹配到的文档数】"executionTimeMillis" : 0, 【选择和查询执行计划所需时间。毫秒】"totalKeysExamined" : 1, 【扫描的索引条目数】"totalDocsExamined" : 1, 【扫描文档数】"executionStages" : { 【最优执行计划的完整信息】"stage" : "FETCH", 【FETCH:根据索引结果去扫描文档】"nReturned" : 1, 【stage=FETCH时,跟上面的nReturned一样】"executionTimeMillisEstimate" : 0, 【检索文档获得数据的时间】"works" : 2, 【执行查询阶段的各个工作以“单元”划分,这里表示工作单元数】"advanced" : 1, 【返回到父阶段的结果数】"needTime" : 0, 【中间结果返回给父级的工作循环次数】"needYield" : 0,"saveState" : 0,"restoreState" : 0,"isEOF" : 1,"docsExamined" : 1,"alreadyHasObj" : 0,"inputStage" : {...}}},"command" : {"find" : "indexDemo","filter" : {"num" : "index:99999"},"$db" : "study"},"serverInfo" : {...},"serverParameters" : {"internalQueryFacetBufferSizeBytes" : 104857600,"internalQueryFacetMaxOutputDocSizeBytes" : 104857600,"internalLookupStageIntermediateDocumentMaxSizeBytes" : 104857600,"internalDocumentSourceGroupMaxMemoryBytes" : 104857600,"internalQueryMaxBlockingSortMemoryUsageBytes" : 104857600,"internalQueryProhibitBlockingMergeOnMongoS" : 0,"internalQueryMaxAddToSetBytes" : 104857600,"internalDocumentSourceSetWindowFieldsMaxMemoryBytes" : 104857600},"ok" : 1
}
关键参数
耗时 executionTimeMillis
- executionStats.executionTimeMillis:整体查询时间。
- executionStats.executionStages.executionTimeMillisEstimate:检索Document获得数据的时间
- executionStats.executionStages.inputStage.executionTimeMillisEstimate:扫描文档 Index所用时间
扫描数 nReturned
- nReturned:查询结果返回的条目数
- totalKeysExamined:总索引扫描的条目数
- totalDocsExamined :总索引扫描的条目数
扫描数越少越好,理想情况:
nReturned = totalKeysExamined = otalDocsExamined
stage 参数表
类型 | 描述 |
---|---|
COLLSCAN | 全表扫描 |
IXSCAN | 索引扫描 |
FETCH | 根据索引去检索指定document |
SHARD_MERGE | 将各分片的返回结果合并 |
SORT | 在内存中进行了排序 |
LIMIT | 限制返回数 |
SKIP | 使用skip进行跳过 |
IDHACK | 针对_id进行的查询 |
SHARDING_FILTER | 通过mongos对分片数据进行查询 |
COUNT | 利用db.coll.explain().count()之类进行count运算 |
TEXT | 全文索引 |
PROJECTION | 限定返回字段时候 |
返回最优与备选计划 allPlansExecution
db.indexDemo.find({num:'index:99999'}).explain("allPlansExecution")
慢查询分析
开启内置查询分析器
-- 开启内置查询分析器
db.setProfilingLevel([0,1,2],m)
0:不记录
1:记录超过阈值m的记录
2:记录所有读写操作-- 例子
db.setProfilingLevel(1,100)
db.setProfilingLevel(2)
查看监听结果
db.system.profile.find().sort({millis:-1}).limit(3)
结果分析
慢查询常见于:
- 应用设计不合理;
- 数据模型不合理;
- 硬件配置;
- 缺少索引;
explain 分析是否跑索引
创建索引
测试数据
db.goods.insertMany( [
{ item: "canvas", qty: 100, size: { h: 28, w: 35.5, uom: "cm" }, status:
"A" },
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status:
"A" },
{ item: "mat", qty: 85, size: { h: 27.9, w: 35.5, uom: "cm" }, status:
"A" },
{ item: "mousepad", qty: 25, size: { h: 19, w: 22.85, uom: "cm" },
status: "P" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status:
"P" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status:
"D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status:
"D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" },
status: "A" },
{ item: "sketchbook", qty: 80, size: { h: 14, w: 21, uom: "cm" }, status:
"A" },
{ item: "sketch pad", qty: 95, size: { h: 22.85, w: 30.5, uom: "cm" },
status: "A" }
]);
db.inventory.insertMany([
{ _id: 1,item: "abc",stock: [{ size: "S", color: "red", quantity: 25 },{
size: "S", color: "blue", quantity: 10 },{ size: "M", color: "blue",
quantity: 50 }]},
{_id:2,item:"def",stock:[{size:"S",color:"blue",quantity:20},
{size:"M",color:"blue",quantity:5},{size:"M",color:"black",quantity:10},
{size:"L",color:"red",quantity:2}]},
{_id:3,item:"ijk",stock:[{size:"M",color:"blue",quantity:15},
{size:"L",color:"blue",quantity:100},{size:"L",color:"red",quantity:25}]}
])
单字段索引 Single Field
单列索引
db.<table>.createIndex(keys, options)
options:
- 1:按照升序创建索引
- -1:按照降序创建索引
db.indexDemo.find({num:"index:600"}).explain()db.indexDemo.createIndex({num:1})db.indexDemo.find({num:"index:600"}).explain()
给嵌入式字段创建索引
{"_id": ObjectId("570c04a4ad233577f97dc459"),"score": 1034,"location": { state: "NY", city: "New York" }
}-- 创建索引
db.<table>.createIndex( { "location.state": 1 } )-- 索引生效
db.<table>.find( { "location.state": "CA" } )
db.<table>.find( { "location.city": "Albany", "location.state": "NY" } )
给整个内嵌文档创建索引
{"_id": ObjectId("570c04a4ad233577f97dc459"),"score": 1034,"location": { state: "NY", city: "New York" }
}-- 创建索引
db.<table>.createIndex( { location: 1 } )-- 索引生效
db.records.find( { location: { city: "New York", state: "NY" } } )
复合索引 Compound Index
db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )
⚠️不能创建具有hashed索引类型的复合索引。如果试图创建包含hashed索引字段的复合索引,将收到一个错误。
{"_id": ObjectId(...),"item": "Banana","category": ["food", "produce", "grocery"],"location": "4th Street Store","stock": 4,"type": "cases"
}-- 创建索引
db.<table>.createIndex( { "item": 1, "stock": 1 } )-- 索引生效
db.<table>.find( { item: "Banana" } )
db.<table>.find( { item: "Banana", stock: { $gt: 5 } } )
⚠️特别注意字段的顺序与排序。复合索引遵循最左匹配原则。
多键索引 Multikey indexes
支持对数组中每个元素创建索引。元素类型:string、number、 nested documents(嵌套文档) 。
number 类型数组
db.inventory.remove({})
db.inventory.insertMany([{ _id: 5, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] },{ _id: 6, type: "food", item: "bbb", ratings: [ 5, 9 ] },{ _id: 7, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] },{ _id: 8, type: "food", item: "ddd", ratings: [ 9, 5 ] },{ _id: 9, type: "food", item: "eee", ratings: [ 5, 9, 5 ] }
])
-- 没加索引
db.inventory.find({ratings:[5,9]}).explain("executionStats")
-- 给数组加索引
db.inventory.createIndex( { ratings: 1 } )
嵌套文档 类型数组
单列索引
db.inventory.dropIndexes()db.inventory.insertMany([{ type: "food", item: "aaa", ratings: [ 5, 8, 9 ], size:[{w:10,h:165},{w:9,h:158}] },{type: "food", item: "aaa", ratings: [ 5, 8, 9 ], size:[{w:11,h:175},{w:7,h:142}] },{type: "food", item: "aaa", ratings: [ 5, 8, 9 ], size:[{w:15,h:163},{w:8,h:157}] }
])
-- 无索引查询
db.inventory.find({"size.w":10}).explain("executionStats")
-- 添加索引
db.inventory.createIndex({"size.w":1})
复合索引
-- 添加复合索引
db.inventory.createIndex( {"size.w":1,"size.h":2})
-- 查询
db.inventory.find({"size.w":10}).explain("executionStats")
db.inventory.find({"size.w":10},{"size.h":10}).explain("executionStats")
地理空间索引 Geospatial Index
- 2dsphere索引,用于存储和查找球面上的点
- 2d索引,用于存储和查找平面上的点
db.company.insert({loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] },name:"来广营地铁站-叶青北园",category : "Parks"}
)
# 2dsphere 或者 2d 。可以建立组合索引。
db.company.ensureIndex( { loc : "2dsphere" } )db.company.find({"loc" : {"$geoWithin" : {"$center":[[116.482451,39.914176],0.05]}}
})
全文索引 Text Index
一个集合最多一个全文索引,可以覆盖多个字段。中文分词支持不佳(推荐ES)。
db.<table>.createIndex({"fieldA": "text"})db.<table>.createIndex({fieldA: "text",fieldB: "text"})db.store.insert([
{ _id: 1, name: "Java Hut", description: "Coffee and cakes" },
{ _id: 2, name: "Burger Buns", description: "Gourmet hamburgers" },
{ _id: 3, name: "Coffee Shop", description: "Just coffee" },
{ _id: 4, name: "Clothes Clothes Clothes", description: "Discountclothing" },
{ _id: 5, name: "Java Shopping", description: "Indonesian goods" }
])db.store.createIndex( { name: "text", description: "text" } )db.store.find( { $text: { $search: "java coffee shop" } } )
{ "_id" : 3, "name" : "Coffee Shop", "description" : "Just coffee" }
{ "_id" : 1, "name" : "Java Hut", "description" : "Coffee and cakes" }
{ "_id" : 5, "name" : "Java Shopping", "description" : "Indonesian goods" }
哈希索引 Hashed Index
只用于等值查询。
db.<table>.createIndex({"字段": "hashed"})
MongoDB 索引底层数据结构
文档类型数据库使用 BSON 格式保存数据,比 RDBMS 存储更方便。RDBMS 适合用于多表之间的关联的场景,而 BSON 可以把关联的数据存在一起。
比如MySQL使用 RDBMS 格式,数据的关联性强,范围查询普遍。所以底层使用B+树。
MongoDB使用B树,通过索引能更快访问,但不适合范围查询。
B树特点:
- 多路搜索。
- 节点存储既存储索引又存储数据。
- 关键字在树中只出现一次。
跟B+树的区别:
- 叶子节点之间的指向。
- 数据保存的位置。