基础知识

mongo 与 docker

mongo 与 java

mongo 的索引

准备工作

数据准备

  [json]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 1 */ { "_id" : ObjectId("5c07bc41fd7bbd038830240f"), "name" : "MongoDB", "type" : "database", "count" : 1, "info" : { "x" : 203, "y" : 102 } } /* 2 */ { "_id" : ObjectId("5c07bd11fd7bbd3914a1ad08"), "name" : "MongoDB", "type" : "database", "count" : 1, "info" : { "x" : 203, "y" : 102 } }

创建索引

我们为 name 和 type 提供索引。

  [plaintext]
1
db.getCollection('person').createIndex({type: -1, name: -1});

查看索引

  [plaintext]
1
db.getCollection('person').getIndex();

内容如下:

  [json]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 1 */ [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.person" }, { "v" : 2, "key" : { "type" : -1.0, "name" : -1.0 }, "name" : "type_-1_name_-1", "ns" : "test.person" } ]
  • 查看索引的总大小
  [plaintext]
1
db.getCollection('person').totalIndexSize();

结果

  [plaintext]
1
49152

删除索引

  • 删除索引
  [plaintext]
1
db.COLLECTION_NAME.dropIndex("INDEX-NAME")

比如 type_-1_name_-1

  • 删除所有的索引
  [plaintext]
1
db.COLLECTION_NAME.dropIndexes()

执行 SQL 查看执行计划

指定 name/type,查看执行计划。

基本使用功能:

  [plaintext]
1
db.getCollection('person').find({name:'MongoDB', type: 'database'}).explain();

结果:

  [json]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "test.person", "indexFilterSet" : false, "parsedQuery" : { "$and" : [ { "name" : { "$eq" : "MongoDB" } }, { "type" : { "$eq" : "database" } } ] }, "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "type" : -1.0, "name" : -1.0 }, "indexName" : "type_-1_name_-1", "isMultiKey" : false, "multiKeyPaths" : { "type" : [], "name" : [] }, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "type" : [ "[\"database\", \"database\"]" ], "name" : [ "[\"MongoDB\", \"MongoDB\"]" ] } } }, "rejectedPlans" : [] }, "serverInfo" : { "host" : "c120a2890d51", "port" : 27017, "version" : "4.0.4", "gitVersion" : "f288a3bdf201007f3693c58e140056adf8b04839" }, "ok" : 1.0 }

执行计划属性解释

返回结果包含两大块信息,一个是queryPlanner,即查询计划,还有一个是serverInfo,即MongoDB服务的一些信息。

那么这里涉及到的参数比较多,我们来一一看一下:

参数 含义
plannerVersion 查询计划版本
namespace 要查询的集合
indexFilterSet 是否使用索引
parsedQuery 查询条件,此处为x=1
winningPlan 最佳执行计划
stage 查询方式,常见的有COLLSCAN/全表扫描、IXSCAN/索引扫描、FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询
filter 过滤条件
direction 搜索方向
rejectedPlans 拒绝的执行计划
serverInfo MongoDB服务器信息

添加不同参数

explain() 也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。

queryPlanner 默认

queryPlanner是默认参数,添加queryPlanner参数的查询结果就是我们上文看到的查询结果,so,这里不再赘述。

executionStats 最佳执行计划

executionStats 会返回最佳执行计划的一些统计信息,如下:

  [plaintext]
1
db.getCollection('person').find({name:'MongoDB', type: 'database'}).explain('executionStats');

最佳执行结果

  [json]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "test.person", "indexFilterSet" : false, "parsedQuery" : { "$and" : [ { "name" : { "$eq" : "MongoDB" } }, { "type" : { "$eq" : "database" } } ] }, "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "type" : -1.0, "name" : -1.0 }, "indexName" : "type_-1_name_-1", "isMultiKey" : false, "multiKeyPaths" : { "type" : [], "name" : [] }, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "type" : [ "[\"database\", \"database\"]" ], "name" : [ "[\"MongoDB\", \"MongoDB\"]" ] } } }, "rejectedPlans" : [] }, "executionStats" : { "executionSuccess" : true, "nReturned" : 2, "executionTimeMillis" : 1, "totalKeysExamined" : 2, "totalDocsExamined" : 2, "executionStages" : { "stage" : "FETCH", "nReturned" : 2, "executionTimeMillisEstimate" : 0, "works" : 3, "advanced" : 2, "needTime" : 0, "needYield" : 0, "saveState" : 0, "restoreState" : 0, "isEOF" : 1, "invalidates" : 0, "docsExamined" : 2, "alreadyHasObj" : 0, "inputStage" : { "stage" : "IXSCAN", "nReturned" : 2, "executionTimeMillisEstimate" : 0, "works" : 3, "advanced" : 2, "needTime" : 0, "needYield" : 0, "saveState" : 0, "restoreState" : 0, "isEOF" : 1, "invalidates" : 0, "keyPattern" : { "type" : -1.0, "name" : -1.0 }, "indexName" : "type_-1_name_-1", "isMultiKey" : false, "multiKeyPaths" : { "type" : [], "name" : [] }, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "type" : [ "[\"database\", \"database\"]" ], "name" : [ "[\"MongoDB\", \"MongoDB\"]" ] }, "keysExamined" : 2, "seeks" : 1, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0 } } }, "serverInfo" : { "host" : "c120a2890d51", "port" : 27017, "version" : "4.0.4", "gitVersion" : "f288a3bdf201007f3693c58e140056adf8b04839" }, "ok" : 1.0 }

executionStats 参数介绍

这里除了我们上文介绍到的一些参数之外,还多了executionStats参数,含义如下:

参数 含义
executionSuccess 是否执行成功
nReturned 返回的结果数
executionTimeMillis 执行耗时
totalKeysExamined 索引扫描次数
totalDocsExamined 文档扫描次数
executionStages 这个分类下描述执行的状态
stage 扫描方式,具体可选值与上文的相同
nReturned 查询结果数量
executionTimeMillisEstimate 预估耗时
works 工作单元数,一个查询会分解成小的工作单元
advanced 优先返回的结果数
docsExamined 文档检查数目,与totalDocsExamined一致

allPlansExecution

allPlansExecution 用来获取所有执行计划,结果参数基本与上文相同,这里就不再细说了。

执行 SQL

  [plaintext]
1
db.getCollection('person').find({name:'MongoDB', type: 'database'}).explain('allPlansExecution');

其他方法

explain() 本身

执行 sql

  [plaintext]
1
db.collection.explain().help()

结果如下

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
Explainable operations .aggregate(...) - explain an aggregation operation .count(...) - explain a count operation .distinct(...) - explain a distinct operation .find(...) - get an explainable query .findAndModify(...) - explain a findAndModify operation .group(...) - explain a group operation .remove(...) - explain a remove operation .update(...) - explain an update operation Explainable collection methods .getCollection() .getVerbosity() .setVerbosity(verbosity)

具体的方法

  [plaintext]
1
db.collection.explain().find().help()

日志结果

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Explain query methods .finish() - sends explain command to the server and returns the result .forEach(func) - apply a function to the explain results .hasNext() - whether this explain query still has a result to retrieve .next() - alias for .finish() Explain query modifiers .addOption(n) .batchSize(n) .comment(comment) .collation(collationSpec) .count() .hint(hintSpec) .limit(n) .maxTimeMS(n) .max(idxDoc) .min(idxDoc) .readPref(mode, tagSet) .showDiskLoc() .skip(n) .snapshot() .sort(sortSpec)

IndexFilter

IndexFilter决定了查询优化器对于某一类型的查询将如何使用index,

indexFilter仅影响查询优化器对于该类查询可以用尝试哪些index的执行计划分析,查询优化器还是根据分析情况选择最优计划。

如果某一类型的查询设定了IndexFilter,那么执行时通过hint指定了其他的index,查询优化器将会忽略hint所设置index,仍然使用indexfilter中设定的查询计划。

IndexFilter可以通过命令移除,也将在实例重启后清空。

IndexFilter的创建

可以通过如下命令为某一个collection建立indexFilter

  • 命令
  [plaintext]
1
2
3
4
5
6
7
8
9
db.runCommand( { planCacheSetFilter: <collection>, query: <query>, sort: <sort>, projection: <projection>, indexes: [ <index1>, <index2>, ...] } )
  • 实例
  [plaintext]
1
2
3
4
5
6
7
8
9
10
db.runCommand( { planCacheSetFilter: "orders", query: { status: "A" }, indexes: [ { cust_id: 1, status: 1 }, { status: 1, order_date: -1 } ] } )

上图针对orders表建立了一个indexFilter,indexFilter指定了对于orders表只有status条件(仅对status进行查询,无sort等)的查询的indexes。

Stage

类型

如explain.queryPlanner.winningPlan.stage和explain.queryPlanner.winningPlan.inputStage等。

文档中仅有如下几类介绍

COLLSCAN

全表扫描

IXSCAN

索引扫描

FETCH

根据索引去检索指定document

SHARD_MERGE

将各个分片返回数据进行merge

但是根据源码中的信息,个人还总结了文档中没有的如下几类(常用如下,由于是通过源码查找,可能有所遗漏)

SORT

表明在内存中进行了排序(与老版本的scanAndOrder:true一致)

LIMIT

使用limit限制返回数

SKIP

使用skip进行跳过

IDHACK

针对_id进行查询

SHARDING_FILTER

通过mongos对分片数据进行查询

COUNT

利用db.coll.explain().count()之类进行count运算

COUNTSCAN

count不使用用Index进行count时的stage返回

COUNT_SCAN

count使用了Index进行count时的stage返回

SUBPLA

未使用到索引的$or查询的stage返回

TEXT

使用全文索引进行查询时候的stage返回

PROJECTION

限定返回字段时候stage的返回

聚合查询

对于聚合的查询

  [sql]
1
2
3
4
{ aggregate: "xxx", pipeline: [ { $unwind: "$xxx" }, { $match: { key1: "value1", key2: "value2", merId: "xxx", time: { $gt: 20190701000000000, $lte: 20190701143451346 } } }, { $group: { _id: "xxx", totalAmount: { $sum: "xxx" } } } ] }

不同的查询计划方式

  • 直接
  [plaintext]
1
db.getCollection('xxx').aggregate(XXX).explain()

可以看到命中的索引

  • 使用 find

但是下面的语句,却是全文扫描。

  [plaintext]
1
db.getCollection('xxx').aggregate(XXX).explain()

mongoTempalate

对于 redis 提供的模板,直接使用 mongoTempalate.executeCommand(XXX) 经验证是可以命中索引的。

参考资料

Indexes

MongoDB索引管理-索引的创建、查看、删除

联合索引

  • 执行计划

Mongo explain 执行结果

MongoDB执行计划获取(db.collection.explain())

Mongo 官方 - 性能分析

MongoDB干货系列2-MongoDB执行计划分析详解(1)

MongoDB干货系列2-MongoDB执行计划分析详解(2)

MongoDB干货系列2-MongoDB执行计划分析详解(3)