MongoDB 常用于大数据的存储,在庞大的数据中查询出我们所要的信息,如果使用普通查询方法,遍历所有文档查询,花费的时间太久了。
假设现在有个集合 person,查询命令:db.person.find( {age: 18} ), 查询所有年龄 18 岁的人,这时需要遍历所有的文档(全表扫描),根据位置信息读出文档,对比 age 字段是否为 18。当然如果只有 4 条文档,全表扫描的开销并不大,但如果集合文档数量到百万、甚至千万上亿的时候,对集合进行全表扫描开销是非常大的,一个查询耗费数十秒甚至几分钟都有可能。这时候我们就需要考虑使用索引了。
其中,地理位置索引支持是 MongoDB 的一大亮点,随着智能手机的兴起,查询当前位置附近的事情变得非常普遍。常见的滴滴、摩拜、OFO 等基于位置进行查询的场景都可以使用 MongoDB 的位置索引。
一、了解并创建一个简单索引
任务描述
本关任务:创建一个单字段索引。
相关知识
为了完成本关任务,你需要掌握: 1.了解索引; 2.索引的分类; 3.如何创建索引。
注:本关实训例子程序请在 mydb 数据库的 person 集合内完成。
了解索引
- 什么是索引:
索引本质上是树,最小的值在最左边的叶子上,最大的值在最右边的叶子上,使用索引可以提高查询速度(而不用全表扫描)。
- 索引的原理:
对某个键按照升续或降续创建索引,查询时首先根据查询条件查找到对应的索引条目,然后找到索引条目对应的文档指针(文档在磁盘上的存储位置),根据文档指针再去磁盘中找到相应的文档,整个过程不需要扫描全表,速度比较快。
每个文档被插入集合时,如果没有给它指定索引
_id
,MongoDB 会自动给它创建一个默认索引_id
,是个 ObjectId 对象。如图1所示:图 1
索引的分类
MongoDB 支持多种类型的索引,主要有以下几种类型:
索引类型 用途 包括单字段索引(Single Field Index) 针对某一键 key 创建了单字段索引,其能加速对 key 字段的各种查询请求,是最常见的索引形式,MongoDB 默认创建的 id 索引也是这种类型。 复合索引 (Compound Index) 复合索引是单字索引的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推。 多 key 索引 (Multikey Index) 当索引的字段为数组时,创建出的索引称为多 key 索引。 哈希索引(Hashed Index) 按照某个字段的hash值来建立索引,目前主要用于 MongoDB Sharded Cluster 的 Hash 分片,哈希索引只能满足字段完全匹配的查询,不能满足范围查询等。 地理位置索引(Geospatial Index) 能很好的解决 O2O 的应用场景,比如:查找附近的美食、查找某个区域内的车站等。 文本索引(Text Index) 能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引。 索引基本操作
- 创建索引(单字段索引)
db.person.createIndex({key:1})
- key :要创建索引的键;
- 如果为 1 说明是按照升序创建索引,而如果为 -1,则是按降序创建索引。
如图2所示,说明创建索引成功:
图 2
查询索引
查询集合索引:
db.person.getIndexes()
效果如图3所示:
图 3
- 查询系统全部索引:
db.system.indexes.find()
删除索引
通过指定索引名称删除该索引:
db.person.dropIndex("ageIdx")
效果如图4所示:
图 4
- 通过指定集合删除集合中的全部索引:
db.person.dropIndexes()
效果如图5所示:
图 5
注:默认索引
_id
不会且不能被删除编程要求
根据提示,在右侧命令行进行操作,在 test 数据库创建集合 student ,内容如下:
_id name age score 1 王小明 15 90 2 周晓晓 18 86 3 王敏 20 96 4 李晓亮 15 74 5 张青青 21 88 然后在集合中创建成绩的降序索引。
> use test
switched to db test
> db.student.insert([{_id:1,name:"王小明",age:15,score:90},{_id:2,name:"周晓晓",age:18,score:86},{_id:3,name:"王敏",age:20,score:96},{_id:4,name:"李晓亮",age:15,score:74},{_id:5,name:"张青青",age:21,score:88}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 5,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.student.createIndex({score:-1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
二、常见索引的创建
任务描述
本关任务:按照编程要求创建不同的索引。
相关知识
为了完成本关任务,你需要掌握: 1.创建复合索引; 2.创建多 key 索引; 3.创建哈希索引; 4.文本索引创建与使用(重点)。
注:本关实训例子程序请在 mydb2 数据库内完成。
创建复合索引
和创建单字段索引的方法差不多,只是选取了多个键一同作为索引,中间以逗号隔开:
db.person.createIndex({age: 1, name: 1})
用法也和单字段索引差不多,就不再赘述。
创建多 key 索引
当索引的字段为数组时,创建出的索引称为多 key 索引,多 key 索引会为数组的每个元素建立一条索引,比如 person 集合加入一个 habbit 字段(数组)用于描述兴趣爱好:
{name : '王小明', age : 19, habbit: ['football', 'runnning']}
需要查询有相同兴趣爱好的人就可以利用 habbit 字段的多 key 索引。
db.person.createIndex( {habbit: 1} ) // 升序创建多key索引 db.person.find({habbit: 'football'}) //查找喜欢足球的人
创建哈希索引
创建命令如下:
db.person.createIndex( { _id: 'hashed' } )
注:哈希索引涉及知识点太多,本关就不做重点介绍,以后用到再做详细解释。
文本索引的创建与使用
- 什么时候使用文本索引;
假如我们用 Mongodb 存储了很多博客文章,那么如何快速找到所有关于 mongodb 这个主题的文章呢?这时候就要用到文本搜索了。
有文章集合 collection,如下:
{ title: 'enjoy the mongodb articles on educoder', tags: [ 'mongodb', 'educoder' ] }
创建文本索引命令,如下:
db.collection.createIndex({ title: 'text'})
- 创建全文本索引的字段必须为 string 格式;
- 每个集合只支持一个文本索引。
运行效果如图1所示:
图 1
也可以创建多个字段的 text,下面的示例在字段主题和注释上创建一个文本索引:
db.collection.createIndex( { title:'text', tags:'text' } )
- 使用文本索引;
现在我们已经创建了 title 的索引,我们来搜索一下含有 educoder.net 的文章:
db.collection.find({$text:{$search:'educoder.net'}})
- search 后的关键词可以有多个,关键词之间的分隔符可以是多种字符,例如空格、下划线、逗号、加号等,但不能是
-
和\
,因为这两个符号会有其他用途。搜索的多个关键字是 or 的关系,除非你的关键字包含-
;- 匹配时不是完整的单词匹配,相似的词也可以匹配到;
运行效果如图2所示:
图 2
删除文本索引;
通过命令获取索引名:
db.collection.getIndexes()
删除命令:
db.collection.dropIndex('title_text')
运行效果如图3所示:
图 3
编程要求
根据提示,在右侧命令行进行操作,在 test2 数据库中创建集合 article,内容如下:
_id title tags follwers 1 提升程序员工作效率的6个工具利器 Alfred,幕布 543 2 我是如何从零开始学习前端的 HTML,Html5,CSS 1570 3 20个非常有用的JAVA程序片段 Java,编程 1920 集合创建完成后,按要求创建以下索引:
用字段 follwers 和 title 创建复合升序索引;
用字段 tags 创建多 key 降序索引;
用
_id
创建哈希索引;用字段 title 和 tags 创建文本索引。
> use test2
switched to db test2
> db.article.insert([
{_id:1,title:"提升程序员工作效率的6个工具利器",tags:["Alfred","幕布"],follwers:543},
{_id:2,title:"我是如何从零开始学习前端的",tags:["HTML","Html5","CSS"],follwers:1570},
{_id:3,title:"20个非常有用的JAVA程序片段",tags:["Java","编程"],follwers:1920}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 3,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.article.createIndex({follwers:1,title:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.article.createIndex({tags:-1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
> db.article.createIndex({_id:'hashed'})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 3,
"numIndexesAfter" : 4,
"ok" : 1
}
> db.article.createIndex({title:'text',tags:'text'})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 4,
"numIndexesAfter" : 5,
"ok" : 1
}
三、有趣的地理位置索引
任务描述
本关任务:查看我们距离哪所大学最近。
相关知识
为了完成本关任务,你需要掌握: 1.什么是 GeoJson 数据; 2.如何创建地理位置索引; 3.如何使用地理位置索引。
注:本关实训例子程序请在 mydb3 数据库内完成。
GeoJson 数据
并不是所有文档都可以创建地理位置索引,只有拥有特定格式的文档才可以创建。
如果我们用的是 2dsphere 索引,那么插入的应该是 GeoJson 数据。GeoJson 的格式如是: { type: 'GeoJSON type' , coordinates: 'coordinates' }
type :指的是类型,可以是 Point (本例中用的)、LineString、 Polygon 等;
coordinates :指的是一个坐标数组。
我们有如下几个位置的坐标信息(具体详情如图1所示),先把它们写入集合 locations 中。
db.locations.insert({_id:1,name:'长沙站',location:{type:'Point',coordinates:[113.018987,28.201215]}}) db.locations.insert({_id:2,name:'湖南师范大学',location:{type:'Point',coordinates:[112.946045,28.170968]}}) db.locations.insert({_id:3,name:'中南大学',location:{type:'Point',coordinates:[112.932175,28.178291]}}) db.locations.insert({_id:4,name:'湖南女子学院',location:{type:'Point',coordinates:[113.014675,28.121163]}}) db.locations.insert({_id:5,name:"湖南农业大学",location:{type:'Point',coordinates:[113.090852,28.187461]}})
图 1
创建地理位置索引
有了规范的文档后,我们就可以使用以下命令,在 location 键上创建一个地理位置索引:
db.locations.createIndex({location:'2dsphere'})
2d :平面坐标索引,适用于基于平面的坐标计算,也支持球面距离计算,不过官方推荐使用 2dsphere 索引;
2dsphere :几何球体索引,适用于球面几何运算;
默认情况下,地理位置索引会假设值的范围是从−180到180(根据经纬度设置)。
如下索引创建成功,效果如图2所示:
图 2
地理位置索引的使用
查询命令:
db.runCommand({ geoNear:'locations', near:{type:'Point',coordinates:[113.018987,28.201215]}, spherical:true, minDistance:1000, maxDistance:8000 })
geoNear :我们要查询的集合名称;
near :就是基于那个点进行搜索,这里是我们的搜索点“长沙站”;
spherical :是个布尔值,如果为 true,表示将计算实际的物理距离,比如两点之间有多少 km,若为 false,则会基于点的单位进行计算 ;
minDistance :搜索的最小距离,这里的单位是米 ;
maxDistance :搜索的最大距离。
查询结果分析:
在 result 中,查到了“湖南农业大学”和“湖南师范大学”,每个文档都加上了一个 dis 字段,它表示这个点离你搜索点的距离。比如说,在结果中 name 为“湖南农业大学”的点的 dis 为7215.061630510019。表示“湖南农业大学”距离搜索点“长沙站”的距离是7215米。这个结果对于 LBS 应用是非常有用的。具体详情如图3所示:
图 3
编程要求
如图4所示,有6个人 A、B、C、D、E、F 的位置,请将这些位置信息插入到数据库 test3 的集合 people 中,并建立地理位置索引 personloc。
查询 A 周围100~3000米有哪些人;
查询 B 周围100~5000米有哪些人;
查询 C 周围3000~8000米有哪些人;
查询 D 周围3000~8000米有哪些人。
图 4
请逐条插入以下信息和位置 GeoJson 数据:
{_id:1,name:'A',personloc:{type:'Point',coordinates:[116.403981,39.914935]}} {_id:2,name:'B',personloc:{type:'Point',coordinates:[116.433733,39.909511]}} {_id:3,name:'C',personloc:{type:'Point',coordinates:[116.488781,39.949901]}} {_id:4,name:'D',personloc:{type:'Point',coordinates:[116.342609,39.948021]}} {_id:5,name:'E',personloc:{type:'Point',coordinates:[116.328236,39.901098]}} {_id:6,name:'F',personloc:{type:'Point',coordinates:[116.385728,39.871645]}}
echo "
db.people.insert({_id:1,name:'A',personloc:{type:'Point',coordinates:[116.403981,39.914935]}});
db.people.insert({_id:2,name:'B',personloc:{type:'Point',coordinates:[116.433733,39.909511]}});
db.people.insert({_id:3,name:'C',personloc:{type:'Point',coordinates:[116.488781,39.949901]}});
db.people.insert({_id:4,name:'D',personloc:{type:'Point',coordinates:[116.342609,39.948021]}});
db.people.insert({_id:5,name:'E',personloc:{type:'Point',coordinates:[116.328236,39.901098]}});
db.people.insert({_id:6,name:'F',personloc:{type:'Point',coordinates:[116.385728,39.871645]}});
db.people.createIndex({personloc:'2dsphere'});
db.runCommand({geoNear:'people',near:{type:'Point',coordinates:[116.403981,39.914935]},spherical:true,minDistance:100,maxDistance:3000});
db.runCommand({geoNear:'people',near:{type:'Point',coordinates:[116.433733,39.909511]},spherical:true,minDistance:100,maxDistance:5000});
db.runCommand({geoNear:'people',near:{type:'Point',coordinates:[116.488781,39.949901]},spherical:true,minDistance:3000,maxDistance:8000});
db.runCommand({geoNear:'people',near:{type:'Point',coordinates:[116.342609,39.948021]},spherical:true,minDistance:3000,maxDistance:8000});
"