MongoDB 索引小结

MongoDB Indexes

  • 默认 _id 会作为collection的索引
  • 对于单个字段的索引, 排序方向并不影响索引的使用
  • 嵌入文档的索引,必须要完全匹配
  • 对于复合索引,可以匹配任意带有前缀的查询
1
{ "item": 1, "location": 1, "stock": 1 }

支持:

1
2
{ item: 1 }
{ item: 1, location: 1 }
  • 排序
1
db.events.createIndex( { "username" : 1, "date" : -1 } )

支持sort:

1
2
db.events.find().sort( { username: -1, date: 1 } )
db.events.find().sort( { username: 1, date: -1 } )

不支持:

1
db.events.find().sort( { username: 1, date: 1 } )

实验

MongoDB 版本

1
2
~ » mongo --version                                     
MongoDB shell version: 3.0.8

单个索引

创建订单表, 插入10000条数据

1
2
3
4
~ » mongo
for(var i = 0; i < 10000; i++) {
db.order.insert({"sku": i+1, "cid": 10000 - i});
}

根据sku查询, 使用explain()进行分析

1
db.order.find({"sku":100}).explain()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.order",
"indexFilterSet" : false,
"parsedQuery" : {
"sku" : {
"$eq" : 100
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"sku" : {
"$eq" : 100
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"ok" : 1
}

观察到 winningPlan 的 stage 为 COLLSCAN全表扫描

添加索引

1
db.order.ensureIndex({"sku":1})

再次根据sku查询

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
> db.order.find({"sku":100}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.order",
"indexFilterSet" : false,
"parsedQuery" : {
"sku" : {
"$eq" : 100
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"sku" : 1
},
"indexName" : "sku_1",
"isMultiKey" : false,
"direction" : "forward",
"indexBounds" : {
"sku" : [
"[100.0, 100.0]"
]
}
}
},
"rejectedPlans" : [ ]
}
}

发现现在 stage 为 IXSCAN, 意为 index scan, 索引扫描, 此时利用了索引

复合索引

创建collection, 添加数据

1
2
3
for(var i = 0; i < 10000; i++) { 
db.order_2.insert({"sku":i+1, "cid":9999-i});
}

添加复合索引 sku_1_cid_1

1
db.order_2.ensureIndex({"sku":1, "cid":1});

观察现象

查询条件 查找方式 是否使用索引
sku : 100 IXSCAN
cid : 100 COLLSCAN
sku:2, cid:9998 IXSCAN
cid:2, sku:9998 IXSCAN

4个字段组成的复合索引支持的查询类型

1
2
3
for(var i = 0; i < 10000; i++) { 
db.order_4.insert({"sku":i+1, "cid":9999-i, "status": i * 100, "order" : i});
}
1
db.order_4.ensureIndex({"sku":1,"cid":1,"status":1,"order":1})

索引 sku_1_cid_1_status_1_order_1

查询条件 查找方式 是否使用索引
sku : 100 IXSCAN
cid : 100 IXSCAN
status : 100 IXSCAN
order : 100 IXSCAN
sku:2, cid:9998 IXSCAN
sku:9998, status: 100 IXSCAN
sku:9998, order: 100 IXSCAN
cid:1, status:100 COLLSCAN
cid:1, order: 0 COLLSCAN
status:100, order:1 COLLSCAN
sku:1,cid:100,status:1 IXSCAN
sku:1,cid:100,order:1 IXSCAN
sku:1,status:100,order:0 IXSCAN
cid:1,status:100,order:0 COLLSCAN
sku:1,cid:100,status:1,order:1 IXSCAN

综上所述:

  • 是否使用索引与查询条件的顺序无关
  • 创建索引时指定的字段顺序很重要
  • 只要查询条件满足索引的前缀就会使用索引
  • 确保你创建的复合索引在大部分的查询语句中使用到前缀(包含最前面的字段)