MongoDB之索引

常用命令

查看表的索引

db.

.getIndexes()

查看表索引的大小

db.

.totalIndexSize()

重建索引

db.

.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.

.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( { : , : , ... } )

⚠️不能创建具有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树,通过索引能更快访问,但不适合范围查询。
MongoDB之索引_第1张图片
B树特点

  • 多路搜索。
  • 节点存储既存储索引又存储数据。
  • 关键字在树中只出现一次。

跟B+树的区别:

  • 叶子节点之间的指向。
  • 数据保存的位置。

你可能感兴趣的:(海量数据存储专栏,mongodb,数据库)