MongoDB之索引小谈

分享一下MongoDB索引的一些知识

Posted by Jeremy Song on 2021-08-02
Estimated Reading Time 8 Minutes
Words 1.9k In Total
Viewed Times

本篇文章简单介绍一下关于 MongoDB 索引的一点小知识。好久没没用 MongoDB,经常回顾复习,才能发现自己之前理解的漏洞。

什么是索引

索引就是用来加速查询的。数据库索引与书籍的索引类似:有了索引就不需要翻遍整本书,数据库则可以直接在索引中查找,使得查找速度能提高几个数量级。在索引中找到条目以后,就可以直接跳转到目标文档的位置。

MongoDB 的索引几平与传统的关系型数据库索引一模一样,绝大多数优化 MySQL/Oracle/SQLite 索引的技巧也同样适用于 MongoDB。

创建索引

创建简单索引

MongoDB 创建索引使用 ensureIndex 函数,函数实现可使用 db.system.ensureIndex 查询,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function (keys, options) {
var result = this.createIndex(keys, options);

if (this.getMongo().writeMode() != "legacy") {
return result;
}

err = this.getDB().getLastErrorObj();
if (err.err) {
return err;
}
// nothing returned on success
}

函数接受两个参数 keysoptions ,其中 keys 表示索引内容,options 表示索引创建附件条件,options 可不传。以下为创建索引的具体示例:

1
2
db.UserInfo.ensureIndex({"userAccount": 1}); // 直接根据userAccount创建升序索引
db.UserInfo.ensureIndex({"userAccount": -1}); // 直接根据userAccount创建降序索引

题外话: 如何查看 MongoDB 查询的执行过程?请看以下示例。

  • 不使用索引的查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
db.UserInfo.find().explain(); // 不使用索引的查询,返回如下:

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.UserInfo",
"indexFilterSet" : false,
"parsedQuery" : {

},
"winningPlan" : {
"stage" : "COLLSCAN",
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "dev-ThinkPad-Edge-E460",
"port" : 27017,
"version" : "4.0.6",
"gitVersion" : "caa42a1f75a56c7643d0b68d3880444375ec42e3"
},
"ok" : 1
}
  • 使用索引的查询
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
db.UserInfo.find({"userAccount":/abc/}).explain();  // 使用索引的查询,返回如下

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.UserInfo",
"indexFilterSet" : false,
"parsedQuery" : {
"userAccount" : {
"$regex" : "abc"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"userAccount" : {
"$regex" : "abc"
}
},
"keyPattern" : {
"userAccount" : 1
},
"indexName" : "userAccount_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"userAccount" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"userAccount" : [
"[\"\", {})",
"[/abc/, /abc/]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "dev-ThinkPad-Edge-E460",
"port" : 27017,
"version" : "4.0.6",
"gitVersion" : "caa42a1f75a56c7643d0b68d3880444375ec42e3"
},
"ok" : 1
}

创建唯一索引

1
2
db.UserInfo.ensureIndex({"userNo":1},{"unique":true}); // 创建一个以UserNo为键的唯一索引
db.UserInfo.ensureIndex({"userNo":1},{"unique":true, "dropDups":true}); // 创建一个以UserNo为键的唯一索引。如果存在重复文档,则保留查询到的第一条,删除其他文档。

注意:其中选项 dropDups 在使用时要特别注意,该操作会删除重复文档,建议操作前备份。

后台创建索引

1
db.UserInfo.ensureIndex({"userAccount":1},{"background":true}); // 后台创建索引

注意:非后台建立索引数据库会阻塞建立索引期间的所有请求,建议生产环境或数据量大的环境使用后台创建索引。

自定义索引名

1
db.UserInfo.ensureIndex({"userAccount":1},{"name":"idx_ua_asc"}); // 直接创建索引并设置索引名称为idx_ua_asc

注意:索引名最长为127字节,大于127字节的索引名创建索引会失败。

查询索引

MongoDB查询索引使用 getIndexes 函数,函数实现可使用 db.system.getIndexes 查询,实现如下:

1
2
3
4
5
6
7
function (filter) {
var res = this._getIndexesCommand(filter);
if (res) {
return res;
}
return this._getIndexesSystemIndexes(filter);
}

函数接受一个 filter 参数为查询条件,filter 可不传。以下为查询索引的具体示例:

1
2
db.UserInfo.getIndexes(); // 查询所有所有索引
db.UserInfo.getIndexes({"name":"idx_ua_asc"}); // 查找名称为idx_au_asc的索引

删除索引

MongoDB 删除索引使用 dropIndexes 函数和 dropIndex 函数删除索引,函数实现可分别使用 db.system.dropIndexesdb.system.dropIndex 查询,函数实现分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// dropIndexes
function () {
if (arguments.length)
throw Error("dropIndexes doesn't take arguments");

var res = this._db.runCommand({deleteIndexes: this.getName(), index: "*"});
assert(res, "no result from dropIndex result");
if (res.ok)
return res;

if (res.errmsg.match(/not found/))
return res;

throw _getErrorWithCode(res, "error dropping indexes : " + tojson(res));
}

// dropIndex
function (index) {
assert(index, "need to specify index to dropIndex");
var res = this._dbCommand("deleteIndexes", {index: index});
return res;
}

其中 dropIndexes 函数删除除过 _id 键索引以外的全部索引,不接受参数;dropIndex 接受一个 index 参数,删除满足条件的指定索引。具体使用示例如下:

1
2
db.UserInfo.dropIndexes(); // 删除所有索引,不包括_id索引
db.UserInfo.dropIndex("idx_ua_asc"); // 删除名称为idx_au_asc的索引

更新索引

更新索引使用方法同创建索引,不再赘述。

指定索引查询

1
db.UserInfo.find({"userAccount":/abc/}).hint({"userAccount":-1}); // 指定索引查询。

注意:慎用!MongoDB查询器会智能选择使用最优的索引查询。

地理位置索引

创建索引

1
db.map.ensureIndex({"gps":"2d"}); // 创建地理位置索引,注意值为"2d"

注意:"gps"键的值必须是某种形式的一对值:一个包含两个元素的数组或是包含两个键的内嵌文档。下面这些都是有效的:

1
2
3
{"gps": [0,100]};
{"gps": {"x":-10, "y":100}};
{"gps": {"latitude":-180, "longitude":180}};

键名可以随意,例如 {"gps":{"foo":100,"bar":100}} 也是可以的。默认情况下,地理空间索引假设值的范围是-180~180,对于经纬度来说非常方便。要是想用其他值,可以通过 ensureIndex 的选项指定最大值最小值。如下:

1
db.map.ensureIndex({"light-year":"2d"},{"min":-1000,"max":1000});

创建复合索引

1
db.map.ensureIndex({"location":"2d", "desc":1}); // 创建地理空间复合索引

查询、删除、修改索引

地理位置索引的查询、删除、修改同上,不再赘述。

关于地理位置查询的题外话

1
2
3
4
5
db.map.find({"gps":{"$near":[20,10]}}); // 查找距离点(20,10)最近的点,默认返回100个
db.map.find({"gps":{"$near":[20,10]}}).limit(10); // 查找距离点(20,10)最近的十个位置
db.runCommand({geoNear:"map", near:[20,10], num:10}); // 查找距离点(20,10)最近的十个位置
db.map.find({"gps":{"$within":{"$box":[[0,0],[20,10]]}}}); // 查找在一个矩形范围内的位置,$box的两个参数依次为矩形左下角坐标和右上角坐标
db.map.find({"gps":{"$within":{"$center":[[0,0],5]}}}); // 查找在一个圆形范围内的位置,$center的两个参数依次为圆心坐标和圆半径

最后

复习是巩固的最好途径。每一次回头尝试都会有新的理解。


欢迎关注我的公众号 须弥零一,跟我一起学习IT知识。


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !