常用命令
查看表的索引
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 - 执行计划 - 听雨危楼 - 博客园
sql
-- 千万测试数据
for(var i=1;i<10000000;i++){ db.indexDemo.insert({_id:i , num:'index:'+i ,address:'address:i%9999'})}
默认的查询计划 queryPlanner
sql
-- 不使用索引
db.indexDemo.find({num:'index:99999'}).explain()
db.indexDemo.createIndex( { num: 1 } )
db.indexDemo.getIndexes()
db.indexDemo.dropIndex("num_1")
json
{
"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
sql
-- 在默认的执行计划信息上,多了 executionStats 信息
db.indexDemo.find({num:'index:99999'}).explain("executionStats")
json
{
"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")
慢查询分析
开启内置查询分析器
sql
-- 开启内置查询分析器
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 分析是否跑索引
创建索引
测试数据
sql
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:按照降序创建索引
sql
db.indexDemo.find({num:"index:600"}).explain()
db.indexDemo.createIndex({num:1})
db.indexDemo.find({num:"index:600"}).explain()
给嵌入式字段创建索引
sql
{
"_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" } )
给整个内嵌文档创建索引
sql
{
"_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索引字段的复合索引,将收到一个错误。
sql
{
"_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 类型数组
sql
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 } )
嵌套文档 类型数组
单列索引
sql
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})
复合索引
sql
-- 添加复合索引
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索引,用于存储和查找平面上的点
json
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)。
json
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
只用于等值查询。
json
db.<table>.createIndex({"字段": "hashed"})
MongoDB 索引底层数据结构
文档类型数据库使用 BSON 格式保存数据,比 RDBMS 存储更方便。RDBMS 适合用于多表之间的关联的场景,而 BSON 可以把关联的数据存在一起。
比如MySQL使用 RDBMS 格式,数据的关联性强,范围查询普遍。所以底层使用B+树。
MongoDB使用B树,通过索引能更快访问,但不适合范围查询。
B树特点:
- 多路搜索。
- 节点存储既存储索引又存储数据。
- 关键字在树中只出现一次。
跟B+树的区别:
- 叶子节点之间的指向。
- 数据保存的位置。