Mongodb支持多种index类型,这相对于其他Nosql数据库而言具有很大的优势,它的索引类型比较接近SQL数据库,所以开发者在mongodb中使用索引将是非常便捷的。索引最大的作用就是提高query的查询性能,如果没有索引,mongodb需要scan整个collection的所有的documents,并筛选符合条件的document,如果有索引,那么query只需要遍历index中有限个索引条目即可,况且index中的条目是排序的,这对“order by”操作也非常有利。
index是一种特殊的数据结构,它以一种易于“traverse”的形式来保存collection数据集的一小部分数据,index保存了指定field或者多个filed的值,并按照filed值的顺序排序,索引条目(entry)的有序性在“相等比较”、range查询操作中可以支撑较高的性能,mongodb可以根据index的顺序返回result。
事实上,mongodb的索引结构和原理,和普通的数据库索引没什么太大区别,数据结构仍然采用B-TREE(以及变种)。
尽管index可以提升query的性能,不过在创建索引时仍需要兼顾一些其他方面的考虑,如果你的collection已经保存了大量数据,此时创建索引将会导致大量的IO操作(内存,磁盘读写),耗时较长;mongodb提供了2种方式:foreground和background,foreground即前台操作,它会阻塞用户对数据的读写操作直到index构建完毕,即任何需要获取read、write锁的操作都会阻塞,默认情况下为foreground;background即后台模式,不阻塞数据读写操作,独立的后台线程异步构建索引,此时仍然允许对数据的读写操作;其中background比foreground更加耗时。我们可以使用此方法指定:
db.<collection>.createIndex({useid : 1},{background : true})
同时在构建索引之前,最好检测一下index是否有重复的,或者当前query是否可以被现有的index覆盖,因为重建重复的index不能对提升性能有任何帮助。为了避免性能问题,我们需要在应用启动前通过getIndexes()方法检测相应的索引是否存在,通常我们需要使用单独的代码来创建索引,而不是在application中。如果在构建索引完成之前,mongodb异常退出,那么当mongodb重启时是否继续重建可以通过storage.indexBuildRetry(配置文件参数)或者--nonIndexBuildRetry(命令行参数)来决定,如果mongodb在构建是遇到错误,比如“dumplicate key error”这种无法自主兼容解决的,则会终止并退出进程。
collection.createIndex(new Document("useid",1),new IndexOptions().unique(true));##java代码 collection.createIndex(new Document("userid",1),new IndexOptions().name("idx_useid"));//指定index的别名
我们可以使用dropIndex来删除索引信息:
collection.dropIndex("idx_userid");##使用index name删除 collection.dropIndex(new Document("userid",1));
同时mongodb还提供reIndex命令,可以重建index,它的原理就是先drop索引,然后重新创建索引。
遍历索引:
ListIndexesIterable<Document> indexes = collection.listIndexes(); MongoCursor<Document> cursor = indexes.iterator(); while (cursor.hasNext()) { Document index = cursor.next(); } cursor.close();
mongodb中提供了hint()方法,可以强制query使用指定的索引,这在某些场景下有特殊的作用。此外,mongodb像其他database一样提供了explain()方法,用来检测query对索引的选择情况。此处不再赘言。
一、索引类型
1、_id:每个document都必须有_id字段,如果不指定,mongodb会默认生成;_id字段为内置的“唯一索引”,整个collection中不会存在_id值相同的document,这是mongodb的内部机制,开发者无法改变,也不需要开发者显式的为_id建立索引,当然也无法remove它。通过_id字段来查询document是性能极高的,所以如果尽可能的通过_id查询或者update数据。
2、单字段索引(Single Filed):即对单个filed建立索引,也是常说的“普通索引”;建立索引时可以指定索引数据的order:正序还是倒序。比如:db.<collection>.createIndex({"score" : 1})表示对score字段创建索引,value值正序存储(1表示正序,-1表示倒序)。
3、组合索引(Compound):对多个filed建立索引,即一个索引entry中包含多个filed值;比如{userid : 1, score : -1}表示对userid和score两个field建立索引,索引条目按照userid正序排列,相同的userid的索引条目再根据score值倒序排列。需要注意组合索引中filed的顺序很重要,需要根据application查询的需要慎重设计,否则可能导致无法使用索引而导致性能低下,这涉及到sort操作问题。
mongodb的一个组合索引最多支持31个字段。
组合索引中字段的组合顺序很重要,这和数据库索引一样,索引的匹配仍然遵循“最左前缀”原则。
索引的存储按照字段值的顺序,比如{useid : 1,score : -1},所有索引条目将首先按照userid正序排列,相同的useid按照score字段值倒序排列;对于单子段索引,字段值的排序方式无论是正序还是倒序,并没有太大影响,因为mongodb可以很简单的来“反转”方向;但是对于组合索引,这种排序方式将直接决定是否支持query的sort操作。上述索引可以支持如下查询:
db.user.find().sort({userid:1}) db.user.find().sort({userid:1,score:-1}) db.user.find().sort({userid:-1,score:1})##排序方向反转 #但不支持 db.user.find().sort({userid:1,score:1}) db.user.find().sort({userid:-1,score:-1})
最左前缀(prefix):索引前缀即必须以一个或者多个字段为开头,比如组合索引{"item" : 1,"location" : 1,"stock": 1},它可以支持query中过滤条件包含:1)item字段 2)item字段和location字段 3)item、location、stock三个字段;如果query过滤条件中不包含item字段将无法使用索引,比如location、stock或者location + stock字段的查询条件。
4、复合键索引(Multikey):对document中的数组字段创建索引,就是“复合键索引”;mongodb将会对数组的每个值创建一个索引条目,但每个条目都引用同一个document。复合键索引允许查询数组中是否包含某个(些)元素;如果创建索引的filed为数组,mongodb会自动创建multikey索引,我们不需要显式指定任何额外的属性。如果已经对某个字段建立了复合键索引,那么此后尝试插入新文档但是此字段不是数组时将会抛出error。不需要需要注意,如果索引包含多个字段时(组合索引),只能有一个字段的值为数组,同时对多个数组字段建立复合键索引将不予支持。
在sharding cluster架构中,复合键所以将不能作为sharding key(分片键),或者说数组字段不能作为分片键;复合键(数组字段参与索引)也不能是hash索引,也不支持“Covered query”,很好理解,因为这在存储层面几乎是无法做到的!!
比如文档:{_id : 1,item: "ABC", ratings: [2, 5, 9]};创建索引{ratings: 1},那么对于此单个document而言,将有3个索引key(索引条目):2、5、9,每个索引key都指向同一个文档。此时我们也许已经了解了mongodb是如何构建复合键索引的数据结构了。
5、地理空间索引(Geospatial):为了支持高效的地址位置数据查询,mongodb提供了2种特殊的索引,2d索引(坐标平面)和2sphere索引(经纬球面),存储数据包括经纬度信息(-180 ~ 180)以及数据表示的几何类型;在目前LBS应用中,通过使用Geo索引,可以实现类似于“附近的人、消息”等功能,我们可以利用现有的地图服务获取位置的经纬度信息,同时配合mongodb Geo索引来完成我们的部分业务应用。不过,mongodb并不是专业的LBS数据库。“2d”和2dsphere两种索引要求的document数据结构是不同的,所以开发者需要注意,我们稍后介绍。
2dsphere:经纬球面,如果数据是球面的位置(球面几何),可以使用此索引,2dsphere索引可以与其他多个字段组合。location的数据结构可以为GeoJSON对象或者为传统的坐标对。【参见GeoJSON】mongodb支持如下几种GeoJSON对象:
1)Point:点
2)LineString:线段
3)Polygon:多边形
4)MultiLineString:多条线段
5)MultiPoint:多点
6)MultiPolygon:多个多变形
7)GeometryCollection
2d:平面坐标,在平面几何(欧几里得几何)中计算距离,我们可以存储一些传统的坐标对,这时就可以使用2d索引,2d索引最多只能与一个其他的字段组合,且2d字段必须做前缀,这与2dsphere是有区别的。不过2d索引是mongodb的旧版特性,在2.6之后,我们可以统一使用2dsphere即可,2d索引可以通过转换成GeoJSON中Point类型。
我们使用Geo索引对坐标数据进行如下类型的查询:
1)包含:可以查询包含(inclusion)在指定的多边形区域内的locations(所对应的documents,或者indexes),使用$geoWithin操作符。2d和2dsphere都支持包含查询。
2)相交(intersection):可以查询与指定的几何体相交的locations,仅支持球面数据,即2dsphere索引,使用$geoIntersects操作。
3)临近(proximity):可以查询与指定的point最近的其他points,使用$near操作,2d和2dsphere都支持。
Geo两种索引都不能作为shard key(sharding cluster模式中),当然可以存储在sharded collection中,只是不能作为shard key。不过在shared collection中,$near和$nearSphere将不支持,而是使用$geoNear替代。(稍后专门介绍这些指令)
6、文本索引(Text):支持对collection中的string内容进行搜索操作,即text search;mongodb内置支持几种语言的分词,不过这种文本搜索的能力与专业的搜索引擎相比,还相当简陋;此外mongodb的文本索引,不支持中文,关于此索引,我们将不再赘言。
7、哈希索引(Hash):主要使用场景为“hash sharding”,即在“Sharding”架构模式中,对sharding key使用hash索引,以便数据的sharding和路由操作。Hash索引只支持值的“相等比较”,不支持range查询。当然如果你确定application的所有query均为某个field的“相等”匹配,也可以为此filed建立hash索引。hash索引只支持一个字段,它不能和其他字段组合,同时也不能是“unique”,使用db.<collection>.createIndex({a : "hashed"})来创建hash索引。
二、索引属性(property)
1、唯一索引(Unique):即索引字段的值是唯一的,全局无重复;如果尝试存入重复的值(write),操作将会被拒绝。可以通过db.<collection>.createIndex({ "userid" : 1}, {unique : true})创建唯一索引。我们可以对一个组合索引使用unique属性,表示它们的组合值不能重复,默认情况下unique为false。如果插入的document不包含某个索引字段,那么此字段的值保存为null,mongodb也会用unique约束这样的文档,即不包含某个字段的文档只能保存一条。比如唯一索引{"x" : 1, "y" : 1},只能插入一条{"x" : "content"}(等同于{"x" : "content","y" : null})。对于hash索引而言,则不能使用unique属性。
unique通常与sparse一起使用。
2、稀疏索引(Sparse):只有当document中包含索引字段时,才会为此文档建立索引,但是这些文档仍然能够保存成功·;因为mongodb的document结构是模式自由的,所以document中不包含某个Field也是“正常的”;稀疏索引将会忽略那些document中不包含索引字段的记录,因此稀疏索引并没有包含collection中的所有文档;非sparse索引,将会为那些缺失的字段存储为null,因此索引包含了全部文档。
我们可以将“Sparse”和“Unique”选项同时约束一个index,以避免包含重复值的document,同时还忽略那些不包含索引字段的document(不建立索引,但是文档仍然会被保存)。
对于2d和2dsphere索引,通常是sparse。
对于sparse属性的组合索引,只有至少包含一个索引字段的document才会建立索引,如果所有的索引字段都缺失,将不会为此文档建立索引。
当sparse与unique同时约束索引时,比如createIndex({"score" : 1}, {sparse : true, unique: true}),如果文档中不包含score字段,那么文档都可以保存成功,但是不会对这些文档建立索引,但是对于包含了score字段的文档,那么score值则不能重复。如果是组合索引,比如createIndex({useid : 1, score : 1},{sparse : true, unique: true}),对于缺失score的文档,只要useid不同均可以建立索引且保存成功,如果useid和score都缺失,文档可以保存但是不会建立索引。
我们已经了解到,sparse索引中可能没有包含所有的文档,因为部分文档可能因缺失索引字段而未能建立索引,但是它们缺失保存在了collection中,那么对于某些需要从全部documents筛选数据的操作将无法使用索引,比如查询语句{x : {$exists : false}}(查询不包含x字段的所有文档),除非你使用hint()方法强制使用此索引。
3、TTL索引:对“date”、“timestamp”类型的字段建立TTL索引,并在索引选项中指定索引保存的时长,那么mongodb将会在一定时间之后自动移除那些“过期”的索引和documents。通过TTL索引,可以实现一个清洗数据的“定时任务”。TTL索引只能支持一个字段(同hash索引,不能对组合索引使用此特性),不能和其他字段组合,即一个索引中只能有一个字段;比如document中created字段类型为date,表示文档插入的时间,我们希望文档在3600秒后过期删除,那么可以通过db.<collection>.createIndex({ "created" : 1},{expireAfterSeconds : 3600})创建索引,其中“expireAfterSeconds”为TTL索引的必须选项。如果document中不包含索引字段,文档将不会过期。
mongodb将会创建一个后台线程,读取index值并删除那些过期的document;在createIndex时,mongodb也会在构建索引的同时检测过期,删除那些过期的数据。TTL索引和众多的缓存服务一样,不能保证数据过期则立即被删除(对用户操作不可见),mongodb扫描数据(从头到尾)是有时间间隔的(60秒),因此过期数据的删除可能存在延迟问题。对于“replica Set”,这个后台线程只在primary上运行,primary上的删除操作同步给secondaries。mongodb的存储引擎的特性决定,这种大量的随机删除会导致严重的存储碎片,索引如果collection中存在TTL索引时,将会开启“usePowerOf2Sizes”选项而且不能修改,“usePowerOf2Sizes”可以降低存储碎片的可能。
collection.createIndex(new Document("created",1),new IndexOptions().expireAfter(15L,TimeUnit.MILLISECONDS)); ##java代码
对于java构建索引,代码样例如上,索引的属性可以通过IndexOptions来指定。
三、索引的设计目的就是提高query性能,稍后我们会介绍如何建立合适的索引,以及索引性能的分析。如果你了解过其他数据库的索引机制,可能还会提到“覆盖索引”(Covered query);如果查询条件和“projection”选项中只包含index字段,那么mongodb的查询结果只需要从索引条目中得到即可,无需scan任何document(磁盘操作、以及载入内存),这就是“Covered query”,性能很高。比如对字段score建立了索引,那么查询:collection.find({score:{"$lt" : 30}}, {score : 1,_id : -1})。
索引交集(intersection):这种特性很多database也支持,即一个query使用多个indexes(通常为2个),取其交集作为result。比如一个query有多个查询字段作为过滤条件,一个index能够完成查询的一部分(过滤结果集),另一个index则完成另一部分,然后mongodb将这两个index使用交集来完成整个query;不过索引交集,是否能够提高查询性能,依赖于特殊的查询和系统本身,所以索引交集并非总是能提高性能,相反也会导致性能低下。
对于replica set环境中,当primary上构建索引完成之后,将会在secondaries上构建,不过在secondaries上将采用background模式,因为background构建较大数据集合的索引非常耗时,我们可以首先将secondary离群,然后手动使用foreground方式构建,完成之后再次加入replica set集合,不过这里有个问题需要注意,primary将会把用户的所有write操作写入oplog中,这是一个capped collection,需要确保oplog collection的容量足够大,以避免secondary离群时间过长而导致无法增量的catch up,此时将会导致一个全量的initial sync(将会更加耗时)。
对于sharded cluster而言,createIndex操作将会被发送到所有的shard-primary上。
四、Intersection
上文已经简单提到了这个概念,对于很多数据库而言,也包含此特性,比如mysql。假如某个collection中有两个索引{qty : 1}和{item : 1},对于collection.find({item : "1234", qty : {$gt : 15}})这个查询语句将有可能使用上述两个索引的intersection来完成查询,不过具体是否真的会使用intersection(以及具体使用了那个索引)则需要explain()方法来检测,因为mongodb并不会总是使用intersection,因为它对性能的提高是不可预测的,有可能是降低性能的。
在intersection中是否选择某个index,仍然遵循“最左前缀”原则。
五、Geo索引
Geo索引可能会在基于LBS方面的应用上有所帮助,接下来我们专门介绍一下。
1、2dsphere
2dsphere数据格式可以为GeoJSON或者普通的坐标对,同时2dsphere是2d的兼容升级版,在新版本中我们直接使用2dsphere即可。2dsphere索引默认为sparse属性,索引可以与其他类型的字段形成组合索引,即一个组合索引中只能有一个字段为2dsphere。2dsphere(包括2d)字段不能作为sharding key,但是可以保存在sharding collection中。比如文档:
{ loc : { type: "Point", coordinates: [ -73.97, 40.77 ] }, name: "Central Park", category : "Parks" }
loc字段存储了一个GeoJSON格式的,有type和coordinates两个字段,分别表示“几何”的类型和坐标位置。通过createIndex({loc : "2dsphere"})创建索引,前文已经提到2dsphere可以与其他类型的字段一起构建成组合索引,2dsphere字段可以位于组合索引的任何位置(不像2d的组合索引,其中2d字段必须为索引的首个字段),比如createIndex({loc : "2dphere", category : -1});需要注意的是2dsphere索引字段存储的必须是GeoJSON对象或者普通的坐标对,对于普通的坐标对,数据结构为:
{ loc : [-20.33, 30,12], category : "AAA", name : "BBB" }
2、2d
2d索引数据能够存储普通的坐标对,这是mongodb较旧版本中支持的特性,它的使用场景类似与2dsphere中的Point数据类型;同时2d索引不支持GeoJSON数据对象,也不能使用2d索引查询collection中GeoJSON数据;此外2d索引和2dsphere一样不能作为sharding key,不过仍然可以在sharding collection中保存2d数据,这与2dsphere一样。不过2d字段参与组合索引时,只能作为第一个字段,这与2dsphere不同;比如document保存2d数据:
{ loc : [-20.33, 30,12], category : "AAA", name : "BBB" }
可以通过createIndex({loc : "2d"}),或者组合索引createIndex({loc : "2d", category : -1})。通过数据结构可以知道,2d数据只能保存Point(点)类型的数据,其中坐标对分别为经度、维度,经度为第一个值,经纬度的值范围为[ -180,180)。
3、GeoJSON对象
在2dsphere中,我们知道GeoJSON是它所支持的标准数据类型,用来存储多种基于球面的地理位置数据(spherical),GeoJSON数据格式为:
{ type : <GeoJSON type>, coordinates : <coordinates>}
type和coordinates两个字段都必填,type表示location的几何类型,coordinates表示其经纬度(经度在先,维度在后),根据type的不同,coordinates的数据结构也有所不同。接下来我们依次介绍几种常用的GeoJSON对象。对于java Driver,需要在3.1.x以上版本才支持GeoJSON API。
1)Point(点)
表示一个坐标点,数据结构样例如下:
{ type : "Point", coordinates : [40,5]}
其中coordinates为一个数组,表示坐标对。我们可以使用java插入一条Point类型的数据:
collection.insertOne(new Document("location",new Point(new Position(20.0,30.1))));
2)LineString(线段)
由2个Point组成,其中coordinates为Point数组
{ "type" : "LineString", "coordinates" : [[20.0, 30.0], [35.0, 45.0]] }
插入一个LineString文档:
List<Position> points = new ArrayList<Position>(); points.add(new Position(20,30)); points.add(new Position(35,45)); collection.insertOne(new Document("location",new LineString(points)));
3)Polygon(多边形)
有多个LineString构成的闭合多边形,数据结构示例:
{ type: "Polygon", coordinates: [ [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ] ] }
最终将有上述几个点依次连接,构成一个闭合的多边形,不过需要注意coordinates是一个数组,且数组的每个元素也是一个数组。
List<Position> positions = new ArrayList<Position>(); positions.add(new Position(0,0)); positions.add(new Position(3,6)); positions.add(new Position(6,1)); positions.add(new Position(0,0));//至少三个边,且最终闭合 collection.insertOne(new Document("location",new Polygon(positions)));
在实际工作中,可能还存在多边形嵌套的情况,即图形有一个“外环”多边形和内环多边形组成,它们中间的区域为实际面积。这种嵌套关系,要求两个多边形的边不能有任何交集,且内环被外环完全包含
,数据结构如下:
{ type : "Polygon", coordinates : [ [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ], [ [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] ] ] }
我们使用java插入一个这种环形多边形:
List<Position> exterior = new ArrayList<Position>(); exterior.add(new Position(0,0)); exterior.add(new Position(3,6)); exterior.add(new Position(6,1)); exterior.add(new Position(0,0));//外环 //[ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] List<Position> holes = new ArrayList<Position>(); exterior.add(new Position(2,2)); exterior.add(new Position(3,3)); exterior.add(new Position(4,2)); exterior.add(new Position(2,2));//中间的空洞 sphere.insertOne(new Document("location",new Polygon(new PolygonCoordinates(exterior,holes))));
4)GeometryCollection(平面集合)
这是一个特殊的数据结构,它表示有多种类型的“图形”组成的集合,比如“Point”、“Polygon”等等。数据结构示例如下:
{ type: "GeometryCollection", geometries: [ { type: "MultiPoint", coordinates: [ [ -73.9580, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.9814, 40.7681 ] ] }, { type: "MultiLineString", coordinates: [ [ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ], [ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ], [ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ], [ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ] ] } ] }
此例表示它由“MultiPoint”和“MultiLineString”两个图形构成。
5)其他
GeoJSON还有其他几种类型的对象,在此不再介绍,比如:MultiPoint表示多个点,MultiLineString表示多个线段,MultiPolygon表示多个多边形等等。
4、Geo索引与查询指令
Geo查询包含“临近”、“交集”、“包含”等等,我们通过使用如下几个专门的指令来完成相关查询。
1)$geoWithin
查询那些完全包含在指定形状(shape)内的地理空间数据的documents,允许有边界(border)重叠,$geoWithin通常指定Polygon或者MultiPolygon参与查询。我们指定的形状可以是一个Polygon类型的GeoJSON对象(单环,或者多环的),或者是一个MultiPolygon类型的GeoJSON。
$geoWithin需要对查询字段建立geo索引,不过建立索引确实可以提高查询性能,2dsphere和2d索引均支持此操作。$geoWithin不会对结果数据进行排序(sort)。比如我们需要查询指定多边形内的所有地理空间数据(只查询GeoJSON类型的documents):
db.places.find( { loc: { $geoWithin: { $geometry: { type : "Polygon" , coordinates: [ [ [ 0, 0 ], [ 3, 6 ], [ 6, 1 ], [ 0, 0 ] ] ] } } } } )
JAVA代码示例如下:
List<Position> positions = new ArrayList<Position>(); positions.add(new Position(0,0)); positions.add(new Position(3,6)); positions.add(new Position(6,1)); positions.add(new Position(0,0));//外环 collection.find(Filters.geoWithin("location", new Polygon(positions)));
2)$geoIntersets
查询与指定的GeoJSON对象表示的图形有“交集”(相交,区域重叠)的地理空间数据,包括仅仅有边界相交的图形,可以指定任意类型的GeoJSON类型参与查询。仅对2dsphere索引数据有效,仅查询类型为GeoJSON类型的documents。
db.places.find( { loc: { $geoIntersects: { $geometry: { type: "Polygon" , coordinates: [ [ [ 0, 0 ], [ 3, 6 ], [ 6, 1 ], [ 0, 0 ] ] ] } } } } )
JAVA代码示例为:
List<Position> positions = new ArrayList<Position>(); positions.add(new Position(0,0)); positions.add(new Position(3,6)); positions.add(new Position(6,1)); positions.add(new Position(0,0));//外环 collection.find(Filters.geoIntersects("location", new Polygon(positions)));
3)$geometry
配合$geoWithin、$geoIntersects、$near、$nearSphere使用,占位符,通过$geometry指定GeoJSON类型数据参与查询,即如果参与查询的不是GeoJSON,则不需要使用$geometry。我们在上述1)、2)中已经看到了对$geometry的使用例子。
4)$near、$nearSphere
这两个指令几乎一样,在查询条件中指定一个Point用来根据距离由近到远,获取相应的documents。这两个指令均可以查询GeoJSON对象,其中$near更倾向于对普通的坐标对查询(基于2d索引),$nearSphere倾向于对GeoJSON对象查询(2dsphere索引),不过$nearSphere也可以支持普通的坐标对。它们的查询样例为:
{ $near: [ <x>, <y> ], $maxDistance: <distance in radians> }
{ $nearSphere: { $geometry: { type : "Point", coordinates : [ <longitude>, <latitude> ] }, $minDistance: <distance in meters>, $maxDistance: <distance in meters> } }
{ $nearSphere: [ <x>, <y> ], $minDistance: <distance in radians>, $maxDistance: <distance in radians> }
JAVA代码样例为:
collection.find(Filters.near("location",new Point(new Position(20.1,30,0)),50d,20d));
在查询条件中我们可以指定$minDistance和$maxDistance,最终处于2者距离之间的document将会由近到远的顺序作为result返回,其中$minDistance表示离指定的point至少多远,此指令仅仅当查询字段为2dsphere索引时才支持,即在2d索引下不能设置此值;$maxDistance表示最远的距离;对于$near,distance值为radians(弧度);对于$nearSphere,distance距离的单位是米,以指定的Point为圆心,distance为半径的圆所覆盖的地理位置数据(GeoJSON documents);如果$nearSphere应用在传统的坐标对上(2d数据),distance则为radians。 mongodb内部会有distance(米)与radians的转换计算。(参见下文)
5)$center
配合$geoWithin查询语句,使用$center指定圆心和半径,返回此区域内的数据,$center仅适用于传统的坐标对(2d),不查询GeoJSON对象。需要对查询字段建立2d索引,且不支持2dsphere索引。
{ <location field>: { $geoWithin: { $center: [ [ <x>, <y> ] , <radius> ] } } }
radius(半径)的数值要根据你的坐标系的测量单位而定。如果你的坐标对,只是简单的平面坐标(自定义坐标系),那么radius就根据你自己的坐标系单位而定;如果是经纬度坐标对,那么radius就需要用实际的距离(米)与地球半径距离做一些换算。
sharding collection中不支持$near和$nearSphere,请使用$geoNear替代,使用方式与它们相同。
6)$centerSphere
配合$geoWithin查询语句,基本原理同$center,只不过它适用于GeoJSON表示的地理位置数据(sphere,球面),半径以radians计量。此指令支持2d和2dsphere索引。
{ <location field>: { $geoWithin: { $centerSphere: [ [ <x>, <y> ], <radius> ] } } }
比如查询经纬度为88W、30N的点,10英里范围内的空间数据,则可以使用如下查询语句(3963.2为地球半径的近似英里数):
db.places.find( { loc: { $geoWithin: { $centerSphere: [ [ -88, 30 ], 10/3963.2 ] } } } )
关于distance与radians的换算方式,【参见此文】
7)$box
配合$geoWithin使用,指定一个方形,查询此区域内的地理位置数据,像$center一样只支持普通的坐标对,不支持对GeoJSON对象的查询;$box需要指定“左下角”和“右上角”的坐标位置。只有2d索引支持$box,2dsphere不支持。
{ <location field>: { $geoWithin: { $box: [ [ <bottom left coordinates> ], [ <upper right coordinates> ] ] } } }
8)$polygon
同$box,语法如下:
{ <location field>: { $geoWithin: { $polygon: [ [ <x1> , <y1> ], [ <x2> , <y2> ], [ <x3> , <y3> ], ... ] } } }
9)其他
尽管2d索引支持对球面距离的查询,不过我们尽可能考虑使用2dsphere索引来替代,如果你的数据是原始的“经纬度”。(如果是自定义坐标系,那么则可以不考虑使用2dsphere)。2d索引支持在欧几里得平面上计算距离查询数据,2d索引也支持如下几个操作命令使用球面(spherical)几何的距离查询,不过通常我们认为2dsphere是查询球面数据的首要选择:$nearSphere、$centerSphere、$near、以及使用{spherical : true}选项的geoNear指令。上述三个查询使用radians来表示距离,其他的查询则不是,对于球面(spherical)查询正常工作,需要将distance转换成radians,或者使用自己的距离单位将radians转换成距离。
距离转换成radians:将距离除以球面的半径(比如地球),使用相同的距离测量单位。
radians转换成距离:radians乘以球面半径(比如地球)。地球的半径近似为3963.2英里或者6378.1千米。
比如如下查询collection中以[-74,40.74]为圆心半径为100英里的区域内的数据:
db.places.find( { loc: { $geoWithin: { $centerSphere: [ [ -74, 40.74 ] ,100 / 3963.2 ] } } } )
$geoNear是在sharding collection中使用,它不是一个普通的操作,而是属于pipleline聚合函数。我们稍后再介绍,请参见【geoNear】
六、索引策略
1、按需创建索引
索引可以提高query的查询效率,当然也会增加write时带来的开支,增加内存、磁盘的使用。
1)如果所有的(大多数)查询都使用了同一个字段,那么可以给此字段建立单一字段索引。
2)如果有些query只使用了一个key,而其他的query则是此key与其他key的组合,那么创建一个组合索引则比创建一个或者多个单一索引更加高效,索引的“最左前缀”不仅可以支持单一key的查询,而且还能兼顾它们的组合查询,且还在一定程度上了减少了因多个索引带来的性能开销。比如索引{x: 1, y : 1}开支持 {x : 1} 和 {x : 1, y : 1}两个key的查询。
2、为了提高查询效率,我们尽可能确保所有的索引数据均可以保存在内存中,这样可以避免索引数据从磁盘索引文件中读取。可以使用db.collection.totalIndexSize()来查看此collection中所有索引的大小。如果你有多个collection,那么索引的总尺寸应该为所有collection的尺寸之和。
当然索引不可能总是被全部载入内存,毕竟索引的尺寸终究有可能远大于内存尺寸,比如当数据量巨大或者系统中创建了多个index。mongodb只会将最近使用的indexes或者“right-most”保存在内存中,这可以更加高效的使用索引。
3、选择性(Selectivity)是判定一个索引筛选数据的能力,越高效的索引其选择性越高,即可以排除掉更多的不匹配数据,更少的数据遍历。比如文档中有一个字段status,这个status有2个合法值“new”、“processed”,如果在status字段上建立索引,那么此索引的选择性就比较低,因为它只能筛选掉一半数据,这也意味着即使索引生效,也需要遍历整个collection中一半的数据才能得到最终结果,事实上对status建立索引是没有必要的。
4、对于sort操作,如果能利用索引的排序直接获去结果,这是性能最好的;不过如果query不能通过index获得sort的结果,那么mongodb只能自己在内存中重新排序,在没有使用索引情况下排序操作所占用内存不得超过32M,否则将中断排序。
对于单字段索引,索引的顺序对sort操作并无影响,应为无论是正序还是倒序,mongodb都可以很简单的翻转,这不会对性能有任何影响。
对于多字段的组合索引中的sort操作,排序字段必须和它们在index中声明的顺序保持一致,比如索引{a : 1, b : 1},将不支持sort( { b : 1, a : 1}),且排序的方向必须和index声明的保持一致,比如上述索引支持{a : 1, b : 1} 和 { a : -1, b : -1},但无法支持{a : 1, b : -1} 和 { a : -1, b : 1}。
如果参与排序的字段(和顺序)和索引字段一样或者是索引的前缀字段,且查询条件中也包含索引的前缀字段,那么mongodb则可以使用索引进行排序,这是一种比较高效的做法。比如索引{ a : 1, b : 1, c : 1},如下查询均可以利用索引排序:
db.data.find().sort( { a: 1 } ) db.data.find().sort( { a: -1 } ) db.data.find().sort( { a: 1, b: 1 } ) db.data.find().sort( { a: -1, b: -1 } ) db.data.find().sort( { a: 1, b: 1, c: 1 } ) db.data.find( { a: { $gt: 4 } } ).sort( { a: 1, b: 1 } )
如果参与排序的字段不是索引的前缀字段,具体是否能够利用索引排序,就要求查询条件的字段需要为索引的最左前缀字段,同时查询条件的字段和sort字段的也应该构成组合索引的左前缀,比如索引 { a : 1, b : 1, c: 1},那么此索引将支持如下几种排序方式:
db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } ) db.data.find( { b: 3, a: 4 } ).sort( { c: 1 } ) db.data.find( { a: 5, b: { $lt: 3} } ).sort( { b: 1 } )
如果查询字段和sort字段不能构成组合索引的左前缀,那么将不能高效的利用索引排序,或者有可能不会使用此索引,比如:
db.data.find( { a: { $gt: 2 } } ).sort( { c: 1 } ) db.data.find( { c: 5 } ).sort( { c: 1 } )
七、geoNear实例
如果collection中geo数据很大,我们需要考虑使用sharding架构设计,在sharding collection中,因为不支持$near、$nearSphere指令,事实上这两个指令反而是应用中最常用的操作,那么我们只能使用$geoNear来替代,用来查询“离指定Point一定距离内的geo数据”;$geoNear是一个聚合方法(Aggregation),不过它比较特殊,聚合方法的pipeline中通常只需要$geoNear一个stage即可,$geoNear这个stage中已经集成了“$match”、“$sort”、“$limit”三个功能,即不再需要在pipeline中额外声明$match、$sort、$limit三个stage。(详细内容,请参考【Aggregation】)
$geoNear操作接受一个查询document,此document中支持如下Filed:
1)spherical:是否为“球面”,默认为false。如果使用2dsphere索引,那么它必须为true,则距离计算使用“米”(meters),参与查询的Point为GeoJSON类型,通常为地理位置的经纬度;如果使用2d索引,那么它为false,参与查询的Point为普通的坐标对,那么距离计算则使用radians,上文中已经提到radians与“米”之间的换算方式。
2)limit或者num:这两个字段含义一致,表示返回数据的总条数。作用同stage $limit。
3)maxDistance:可选值,表示离指定Point最大的距离,对于GeoJSON而言,distance的值为“米”,如果是普通坐标对,则为radians。
4)minDistance:可选值,指离指定Point最小的距离。(3.2+版本)
5)query:可选值,筛选文档的查询条件,同stage $match。
6)distanceField:必填值,在输出的结果文档中增加一个字段,用来表示计算的距离,即location与指定的Point之间的距离。
7)distanceMultiplier:可选值,数字类型,此值将会与“计算的距离”相乘,通常用来将距离转换,在2d数据、传统坐标对中使用(即spherical : false)。比如在2d数据中,计算的距离为radians,可以指定distanceMultiplier为地球的半径,那么最终返回的“distanceFiled”值为radians * distanceMultiplier,此时得到的是值为“米”(或者千米,取决于distanceMultiplier的单位);这样结果就和2dsphere(即spherical : true)查询的结果一致了。
8)near:必填值,格式为GeoJSON(2dsphere)或者普通的坐标对(2d),指定参与比较的Point。
当使用$geoNear时需要考虑到:
1)它必须为pipeline的首个stage。
2)必须包含distanceField,此字段将会在结果中输出。
3)我们发现,$geoNear没有指定那个字段和“near”比较,所以这硬性要求collection中有且只有一个字段是2dsphere或者2d索引。这样$geoNear就直接使用索引字段参与查询。
如下为$geoNear的shell查询方式语法:
db.places.aggregate([ { $geoNear: { near: { type: "Point", coordinates: [ -73.99279 , 40.719296 ] }, distanceField: "dist.calculated", maxDistance: 2, query: { type: "public" }, includeLocs: "dist.location", num: 5, spherical: true } } ])
如下为一个完整的JAVA示例:
1)创建索引
MongoClient mongoClient = new MongoClient("127.0.0.1",27017); //mongos MongoDatabase db = mongoClient.getDatabase("test"); MongoCollection<Document> collection = db.getCollection("geo_sample"); collection.createIndex(new Document("location","2dsphere"));
2)插入geo数据
//insert some test datas; List<Document> locations = new ArrayList<Document>(); locations.add(new Document("type","public") .append("location",new Point(new Position(-73.99279,40.719296)))); locations.add(new Document("type","public") .append("location",new Point(new Position(-73.99378,40.719396)))); locations.add(new Document("type","public") .append("location",new Point(new Position(-73.99379,40.719395)))); collection.insertMany(locations);
3)聚合方法查询
//查询 List<Document> pipeline = new ArrayList<Document>(); Document geoNear = new Document(); Document content = new Document(); content.append("near",new Point(new Position(-73.99279,40.719296))); content.append("distanceField","distance"); content.append("maxDistance",100); content.append("query",new Document("type","public")); content.append("spherical",true); content.append("limit",20); geoNear.append("$geoNear", content); pipeline.add(geoNear); MongoCursor<Document> cursor = collection.aggregate(pipeline).allowDiskUse(true) .batchSize(128) .maxTime(60,TimeUnit.SECONDS) .useCursor(true).iterator(); while (cursor.hasNext()) { Document document = cursor.next(); System.out.println(document.toJson()); } cursor.close();
4)查询结果样例
{ "_id": { "$oid": "563ad23af4f64b0d42eadae4" }, "type": "public", "location": { "type": "Point", "coordinates": [ -73.99279, 40.719296 ] }, "distance": 0 } { "_id": { "$oid": "563ad28df4f64b0d45c9780a" }, "type": "public", "location": { "type": "Point", "coordinates": [ -73.99378, 40.719396 ] }, "distance": 84.26495157209176 } { "_id": { "$oid": "563ad28df4f64b0d45c9780b" }, "type": "public", "location": { "type": "Point", "coordinates": [ -73.99379, 40.719395 ] }, "distance": 85.08684215614285 }
相关推荐
从提供的文件内容中,我们可以提取出以下关于MongoDB的知识点: 1. 关于NoSQL的知识点: ...通过这些知识点,我们可以更好地理解MongoDB的设计理念、优势、特性和应用方法,以便在实际开发中做出合适的数据存储选择。
MongoDB是一个分布式文档型...通过运行这个压缩包中的示例代码,你将能够亲身体验到如何在MongoDB中创建、读取、更新和删除数据,以及如何利用其高级特性进行更复杂的操作。这将为你的MongoDB学习之旅打下坚实的基础。
2. MongoDB 6.0.5版本的新特性与改进: - 性能优化:可能包括查询优化、索引构建速度提升、写操作性能增强等。 - 安全性增强:可能包含身份验证、加密、审计日志等方面的改进,以提高数据安全性。 - 新增功能:...
不过,为了更好地利用MongoDB的功能,还需要了解其文档型数据模型、查询语言(MQL)、聚合框架以及地理空间索引等特性。同时,对于性能优化和安全性也需要有深入的理解,例如,合理设置索引、使用连接池以及实施访问...
MongoDB的索引是数据库性能优化的关键因素,与MySQL、Oracle等关系型数据库中的索引原理相似,但具有自身的特性和限制。MongoDB的索引建立在Collection(表)级别,采用B-树数据结构来加速查询和排序操作。 1. 默认...
它以其面向文档的特性、易于扩展性和高性能而著称,成为了现代应用开发中非常受欢迎的选择之一。 **MongoDB** 这个名字来源于英语单词 “humongous”,意指海量的数据。这体现了 MongoDB 的设计初衷是为了应对大...
- MongoDB 4.0引入了多文档事务,MongoDB Java驱动2.5.3可能已经支持这一特性,尽管详细实现可能在后续版本中得到增强。 9. **错误处理**: - `MongoException`及其子类用于抛出各种错误情况,如网络问题、查询...
5. **索引(Index)**: 索引用于提高查询性能,MongoDB支持多种类型的索引,如单字段索引、复合索引等。 #### 三、MongoDB核心概念 **本书深入介绍了MongoDB的核心概念和技术细节:** 1. **数据模型**: 包括如何...
在MongoDB中,数据组织的核心概念包括数据库(database)、集合(collection)、文档(document)和索引(index)。让我们详细了解一下这些概念。 1. 数据库(Database) 在MongoDB中,数据库是数据存储的基本单元...
在这个“MongoDB基础”的资料中,我们将会深入探讨MongoDB的基础知识,包括数据库操作、集合操作、索引创建以及聚合框架的使用。 1. **数据库操作**: MongoDB中的数据库是存储数据的逻辑单元。你可以通过`use ...
MongoDB是目前流行的NoSQL数据库之一,它以高性能、高可用性和易于扩展的特性受到开发者的青睐。本篇学习笔记将从基础操作到高级功能,详细阐述MongoDB的关键知识点。 首先,MongoDB的基本单元是集合(collection)...
MongoDB是一种流行的开源、分布式文档型数据库,常用于处理海量数据和构建实时应用程序。它以其灵活性、可扩展性和高性能...在实际开发中,还需要关注MongoDB的最新版本和特性,以及最佳实践以确保系统的稳定性和性能。
本总结将深入探讨MongoDB的核心概念、特性和实际应用。 ### 1. MongoDB核心概念 - **文档(Document)**: MongoDB以JSON格式存储数据,每个文档都是一个键值对的集合,类似于传统数据库的记录。 - **集合...
MongoDB中的数据模型基于文档,每个文档由键值对组成,键是字符串,值可以是多种数据类型,包括数组和嵌套文档。集合是文档的集合,类似于关系数据库中的表,但不强制列定义和行顺序。 四、查询操作 MongoDB提供了...
在这个离线文档中,你将找到关于MongoDB所有核心操作的详细说明。 1. 数据库操作: - `db`: MongoDB中的`db`对象代表当前数据库,可以用来创建、删除和切换数据库。 - `use <dbname>`: 切换到指定的数据库,如果...
#### 一、MongoDB特性概览 **1.1 MongoDB特点** - **NoSQL数据库:** MongoDB是一种非关系型数据库,基于文档存储,使用BSON(Binary JSON)格式来存储和查询数据。 - **JSON风格文档:** 数据以类似于JSON的结构...
2. **设置数据库连接**:在Node.js应用中配置MongoDB连接,可能在`config`文件夹下。 3. **创建API**:使用Express定义路由,处理HTTP请求,如GET、POST、PUT和DELETE,实现CRUD(创建、读取、更新、删除)操作。 4....
MongoDB是由C++语言编写的,它在非关系型数据库领域中占据重要地位,同时具备一些关系数据库的特性,如强大的查询语言。 在MongoDB中,数据组织方式与传统的关系型数据库有所不同。它不使用表格和行/列结构,而是...
标题中的“PyPI 官网下载 | mongodbshell-0.1a4.tar.gz”表明这是一个在Python Package Index(PyPI)上发布的软件包,名为`mongodbshell`,版本为0.1a4,且文件格式为tar.gz。PyPI是Python开发者发布自己编写的模块...