MongoDB 常用于大数据存储,在庞大的数据中查询出我们所要的信息,如果使用普通查询方法,遍历所有文档查询,花费的时间太久了。

假设现在有个集合 person,查询命令:db.person.find( {age: 18} ), 查询所有年龄 18 岁的人,这时需要遍历所有的文档(全表扫描),根据位置信息读出文档,对比 age 字段是否为 18。当然如果只有 4 条文档,全表扫描的开销并不大,但如果集合文档数量到百万、甚至千万上亿的时候,对集合进行全表扫描开销是非常大的,一个查询耗费数十秒甚至几分钟都有可能。这时候我们就需要考虑使用索引了。

其中,地理位置索引支持是 MongoDB 的一大亮点,随着智能手机的兴起,查询当前位置附近的事情变得非常普遍。常见的滴滴、摩拜、OFO 等基于位置进行查询的场景都可以使用 MongoDB 的位置索引。

本实训需要你有 MongoDB 数据库的基础,没有的话可以学习一下实训 MongoDB 数据库基本操作

目录

第1关:了解并创建一个简单索引

了解索引

索引的分类

索引基本操作

编程要求

第2关:常见索引的创建

创建复合索引

创建多 key 索引

创建哈希索引

文本索引的创建与使用

使用文本索引;

删除文本索引;

编程要求

第3关:有趣的地理位置索引

GeoJson 数据

创建地理位置索引

编程要求


第1关:了解并创建一个简单索引

一、本关任务:创建一个单字段索引。

二、相关知识

为了完成本关任务,你需要掌握: 1.了解索引; 2.索引的分类; 3.如何创建索引。

注:本关实训例子程序请在 mydb 数据库的 person 集合内完成。

了解索引

  • 什么是索引:

    索引本质上是树,最小的值在最左边的叶子上,最大的值在最右边的叶子上,使用索引可以提高查询速度(而不用全表扫描)。

  • 索引的原理:

    对某个键按照升续或降续创建索引,查询时首先根据查询条件查找到对应的索引条目,然后找到索引条目对应的文档指针(文档在磁盘上的存储位置),根据文档指针再去磁盘中找到相应的文档,整个过程不需要扫描全表,速度比较快。

每个文档被插入集合时,如果没有给它指定索引_id,MongoDB 会自动给它创建一个默认索引_id,是个 ObjectId 对象。如图所示:

索引的分类

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,则是按降序创建索引。

如图所示,说明创建索引成功: 

  • 查询索引

    • 查询集合索引:
    db.person.getIndexes()

  • 查询系统全部索引:db.system.indexes.find()

  • 删除索引

    • 通过指定索引名称删除该索引:
    db.person.dropIndex("ageIdx")

通过指定集合删除集合中的全部索引:

db.person.dropIndexes()注:默认索引_id不会且不能被删除

 

编程要求

根据提示,在右侧命令行进行操作,在 test 数据库创建集合 student ,内容如下:

_idnameagescore
1王小明1590
2周晓晓1886
3王敏2096
4李晓亮1574
5张青青2188

然后在集合中创建成绩的降序索引。

 

> 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
}

第2关:常见索引的创建

一、本关任务:按照编程要求创建不同的索引。

二、相关知识

为了完成本关任务,你需要掌握: 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 格式;每个集合只支持一个文本索引。

运行效果如图所示:


也可以创建多个字段的 text,下面的示例在字段主题和注释上创建一个文本索引:

db.collection.createIndex({title:'text',tags:'text'})  

  • 使用文本索引;

    现在我们已经创建了 title 的索引,我们来搜索一下含有 educoder.net 的文章:

    db.collection.find({$text:{$search:'educoder.net'}})

    运行效果如图所示:

    • search 后的关键词可以有多个,关键词之间的分隔符可以是多种字符,例如空格、下划线、逗号、加号等,但不能是-\,因为这两个符号会有其他用途。搜索的多个关键字是 or 的关系,除非你的关键字包含-

    • 匹配时不是完整的单词匹配,相似的词也可以匹配到;

  • 删除文本索引;

    • 通过命令获取索引名:db.collection.getIndexes()

    • 删除命令:db.collection.dropIndex('title_text')

    • 运行效果如图所示:

       

编程要求

根据提示,在右侧命令行进行操作,在 test2 数据库中创建集合 article,内容如下:

_idtitletagsfollwers
1提升程序员工作效率的6个工具利器Alfred,幕布543
2我是如何从零开始学习前端的HTML,Html5,CSS1570
320个非常有用的JAVA程序片段Java,编程1920
为减少你的工作量,文档如下:
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}])

集合创建完成后,按要求创建以下索引:

  • 用字段 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
}

 

 

第3关:有趣的地理位置索引

一、本关任务:查看我们距离哪所大学最近。

二、相关知识

为了完成本关任务,你需要掌握: 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]}})

 

创建地理位置索引

有了规范的文档后,我们就可以使用以下命令,在 location 键上创建一个地理位置索引:

db.locations.createIndex({location:'2dsphere'})
  • 2d :平面坐标索引,适用于基于平面的坐标计算,也支持球面距离计算,不过官方推荐使用 2dsphere 索引;

  • 2dsphere :几何球体索引,适用于球面几何运算;

  • 默认情况下,地理位置索引会假设值的范围是从−180到180(根据经纬度设置)。

如下索引创建成功,效果如图所示:

地理位置索引的使用

查询命令:

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所示:

编程要求

如图4所示,有6个人 A、B、C、D、E、F 的位置,请将这些位置信息插入到数据库 test3 的集合 people 中,并建立地理位置索引 personloc。

  • 查询 A 周围100~3000米有哪些人;

  • 查询 B 周围100~5000米有哪些人;

  • 查询 C 周围3000~8000米有哪些人;

  • 查询 D 周围3000~8000米有哪些人。

请逐条插入以下信息和位置 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]}}

测试说明

上述操作共有11条命令,请按要求填入右侧代码栏 Begin-End 中,每条命令以号隔开。

填写完成之后点击评测,平台会对你的命令进行测试,如果成功,平台会输出如测试集所示的结果,否则会显示报错信息。

提示 : 如果右侧命令行无响应,是因为云端二十分钟会自动关闭容器,重新刷新页面即可解决问题,刷新之后环境会重置,所以你还需要重新创建数据库和集合。

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});
"

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐