0. 什么是索引
索引就是用来加速查询的。数据库索引与书籍的索引类似:有了索引就不需要翻遍整本书,数据库则可以直接在索引中查找,使得查找速度能提高几个数量级。在索引中找到条目以后,就可以直接跳转到目标文档的位置。
MongoDB的索引几平与传统的关系型数据库索引一模一样,绝大多数优化 MySQL/Oracle/SQLite索引的技巧也同样适用于 MongoDB。
1. 创建索引
1.1 创建简单索引
MongoDB创建索引使用ensureIndex函数,函数实现可使用db.system.ensureIndex查询,实现如下:
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
}
函数接受两个参数keys和options,其中keys表示索引内容,options表示索引创建附件条件,options可不传。以下为创建索引的具体示例:
db.UserInfo.ensureIndex({"userAccount": 1}); // 直接根据userAccount创建升序索引
db.UserInfo.ensureIndex({"userAccount": -1}); // 直接根据userAccount创建降序索引
题外话:如何查看MongoDB查询的执行过程?请看以下示例。
- 不使用索引的查询
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
}
- 使用索引的查询
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.3 后台创建索引
db.UserInfo.ensureIndex({"userAccount":1},{"background":true}); // 后台创建索引
注意:非后台建立索引数据库会阻塞建立索引期间的所有请求,建议生产环境或数据量大的环境使用后台创建索引。
1.4 自定义索引名
db.UserInfo.ensureIndex({"userAccount":1},{"name":"idx_ua_asc"}); // 直接创建索引并设置索引名称为idx_ua_asc
注意:索引名最长为127字节,大于127字节的索引名创建索引会失败。
2. 查询索引
MongoDB查询索引使用getIndexes函数,函数实现可使用db.system.getIndexes查询,实现如下:
function (filter) {
var res = this._getIndexesCommand(filter);
if (res) {
return res;
}
return this._getIndexesSystemIndexes(filter);
}
函数接受一个filter参数为查询条件,filter可不传。以下为查询索引的具体示例:
db.UserInfo.getIndexes(); // 查询所有所有索引
db.UserInfo.getIndexes({"name":"idx_ua_asc"}); // 查找名称为idx_au_asc的索引
3. 删除索引
MongoDB删除索引使用dropIndexes函数和dropIndex函数删除索引,函数实现可分别使用db.system.dropIndexes和db.system.dropIndex查询,函数实现分别如下:
// 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参数,删除满足条件的指定索引。具体使用示例如下:
db.UserInfo.dropIndexes(); // 删除所有索引,不包括_id索引
db.UserInfo.dropIndex("idx_ua_asc"); // 删除名称为idx_au_asc的索引
4. 更新索引
更新索引使用方法同创建索引
5. 指定索引查询
db.UserInfo.find({"userAccount":/abc/}).hint({"userAccount":-1}); // 指定索引查询。
注意:慎用!MongoDB查询器会智能选择使用最优的索引查询。
6. 地理位置索引
6.1 创建索引
db.map.ensureIndex({"gps":"2d"}); // 创建地理位置索引,注意值为"2d"
注意:"gps"键的值必须是某种形式的一对值:一个包含两个元素的数组或是包含两个键的内嵌文档。下面这些都是有效的:
{"gps": [0,100]};
{"gps": {"x":-10, "y":100}};
{"gps": {"latitude":-180, "longitude":180}};
键名可以随意,例如{"gps":{"foo":100,"bar":100}}也是可以的。默认情况下,地理空间索引假设值的范围是-180~180,对于经纬度来说非常方便。要是想用其他值,可以通过ensureIndex的选项指定最大值最小值。如下:
db.map.ensureIndex({"light-year":"2d"},{"min":-1000,"max":1000});
6.2 创建复合索引
db.map.ensureIndex({"location":"2d", "desc":1}); // 创建地理空间复合索引
6.3 查询、删除、修改索引
地理位置索引的查询、删除、修改同上,不再赘述。
6.4 关于地理位置查询的题外话
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的两个参数依次为圆心坐标和圆半径