索引类型
MongDB的索引分为以下几种类型:单键索引、复合索引、地理空间索引、全文本索引和哈希索引
单键索引(Single Field Indexes)
在一个键上创建的索引就是单键索引,单键索引是最常见的索引,如MongoDB默认创建的_id的索引就是单键索引。
例子:
{ "_id" : ObjectId(...), "name" : "Alice", "score" : 27 }
如果要在如上的文档中创建单键索引,语句如下:
db.users.ensureIndex( { "score" : 1 } )
其存储结构如下图:
如果想要在子文档的一个键上建立单键索引,其例子如下:
{ "_id": ObjectId(...), "name": "John Doe", "address": { "street": "Main", "zipcode": "53511", "state": "WI" } }
结构如上,其创建语句如下:
db.users.ensureIndex( { "address.zipcode": 1 } )
如果想要在整个子文档上建立单键索引,其例子如下:
{ _id: ObjectId(...), metro: { city: "New York", state: "NY" }, name: "Giant Factory" }
结构如上,其创建语句如下:
db.factories.ensureIndex( { metro: 1 } )
下面的语句能够使用其索引查找到上面的数据:
db.factories.find( { metro: { city: "New York", state: "NY" } } )
但再下面的这一条语句就查不到数据了,说明子文档的查找必须是精确匹配,包括子文档中的顺序:
db.factories.find( { metro: { state: "NY", city: "New York" } } )
复合索引(Compound Indexes)
在多个键上建立的索引就是复合索引。
例子:
{ "_id": ObjectId(...), "userid": "aa1", "category": ["food", "produce", "grocery"], "location": "4th Street Store", "score": 4 }
如果要在如上的文档中创建复合索引,语句如下:
db.products.ensureIndex( { "userid": -1, "score": 1 } )
userid是正序排列,score是逆序排列的。其存储结构如下图:
这个索引可以支持如下的排序:
db.products.find().sort( { userid: 1, score: -1 } ); db.products.find().sort( { userid: -1, score: 1 } ); db.products.find().sort( { userid: 1 } ); db.products.find().sort( { userid: -1 } );
不能支持如下的排序:
db.products.find().sort( { userid: 1, score: 1 } ); db.products.find().sort( { userid: -1, score: -1 } ); db.products.find().sort( { score: 1 } ); db.products.find().sort( { score: -1 } );
多键索引(Multikey Index)
如果在一个数组上面创建索引,MongoDB会自己决定,是否要把这个索引建成多键索引。
如果数据结构如下(两种):
{a: [1, 2], b: 1} {a: 1, b: [1, 2]}
你可以创建{ a: 1, b: 1 },会是多键复合索引。
多键索引结构如下:
例子:
{ "_id" : ObjectId("..."), "name" : "Warm Weather", "author" : "Steve", "tags" : [ "weather", "hot", "record", "april" ] }
文档结构如上,如果你在tags上创建索引,就会创建出多键索引
如果文档结构如下:
{ "_id": ObjectId(...), "title": "Grocery Quality", "comments": [{ author_id: ObjectId(...), date: Date(...), text: "Please expand the cheddar selection." }, { author_id: ObjectId(...), date: Date(...), text: "Please expand the mustard selection." }, { author_id: ObjectId(...), date: Date(...), text: "Please expand the olive selection." }] }
创建{ "comments.text": 1 } 索引也会是多键索引,且在如下查找语句中有效:
db.feedback.find( { "comments.text": "Please expand the olive selection." } )
地理空间索引(Geospatial Indexes and Queries)
MongoDB支持几种类型的地理空间索引。其中最常用的是 2dsphere 索引(用于地球表面类型的地图)和 2d 索引(用于平面地图和时间连续的数据)。
1) 2dsphere
2dsphere 允许使用GeoJSON格式(http://www.geojson.org)指定点、线和多边形。
点可以用形如[longitude, latitude]([经度, 纬度])的两个元素的数组表示:
{ "name" : "New York City", "loc" : { "type" : "Point", "coordinates" : [50, 2] } }
线可以用一个由点组成的数组来表示:
{ "name" : "Hudson River", "loc" : { "type" : "LineString", "coordinates" : [[0, 1], [0, 2], [1, 2]] } }
多边形是由线组成的数组来表示:
{ "name" : "New England", "loc" : { "type" : "Polygon", "coordinates" : [[[0, 1], [0, 2], [1, 2], [0, 1]]] } }
2dsphere 支持 Point、MultiPoint、LineString、MultiLineString、Polygon、MultiPolygon、Geometry Collection
loc 字段的名字可以是任意的,但是其中子对象是由 GeoJSON 指定的,不能改变。
在 ensureIndex 中使用 2dsphere 选项就可以创建一个地理空间索引:
db.world.ensureIndex({"loc": "2dsphere"})
可以使用多种不同的地理空间查询:交集(intersection)、包含(within)以及接近(nearness)。查询时,需要将希望查找的内容指定为形如 {"$geometry":geoJsonDesc} 的 GeoJSON 对象。
交集(intersection),使用 $geoIntersects 操作符:
var place = { "type" : "Polygon", "coordinates" : [[[0, 1], [0, 3], [50, 2], [0, 1]]] } db.world.find({"loc" : {"$geoIntersects" : {"$geometry" : place}}})
会查找出所有与 place 有交集的文档。
包含(within),使用 $within 或者 $geoWithin 操作符:
db.world.find({"loc" : {"$within" : {"$geometry" : place}}})
接近(nearness),使用 $near 或者 $geoNear 操作符:
var place = { "type" : "Point", "coordinates" : [0, 3] } db.world.find({"loc" : {"$near" : {"$geometry" : place}}})
place 必须是个点,$near 是唯一一个会对查询结果进行自动排序的地理空间操作符,$near 的返回结果是按照距离由近及远排序的。
2) 2d
2d 索引用于扁平化表面,而不是球体表面,否则极点附近会出现大量的扭曲变形。
文档中使用包含两个元素的数组表示 2d 索引字段,不是 GeoJSON 的格式。
{ "name": "Water Temple", "tile": [32, 22] }
2d 索引只能对点进行索引。可以保存一个由点组成的数组,但是它只会被保存为由点组成的数组,不会被当成线。特别是对 $within 查询来说,数组中的某个点在查询范围内,该文档就会被找出。
在 ensureIndex 中使用 2d 选项就可以创建一个地理空间索引,也可以在其中设置最大最小边界值和精度。默认情况下,最大值和最小值的范围是[ -180 , 180 ),精度是26位的精度,大致相当于2英尺或60厘米的精度:
db.places.ensureIndex({"tile" : "2d"}, {"min" : -90, "max" : 90, "bits" : 20})
这会创建一个180*180大小的空间索引。
2d 索引的查询比 2dsphere 简单许多,可以直接使用$near 和 $within,而不必带有 $geometry 子对象:
db.places.find({"tile": {"$near": [20, 21]}}).limit(10)
如果不加 limit,默认最多返回100条。
$within 可以查询出某个形状里的所有文档,可以是矩形($box)、圆形($center)或者多边形($polygon)。
db.places.find({"tile": {"$within": {"$box": [[0, 0], [30, 30]]}}})
$box 接收两个元素,第一个元素是矩形的左下角坐标,第二个元素是矩形的右上角坐标。
db.places.find({"tile": {"$within": {"$center": [[30, 30], 10]}}})
$center 也接收两个元素,第一个元素是圆心点的坐标,第二个是圆的半径。
db.places.find({"tile": {"$within": {"$polygon": [[0, 0], [30, 30], [0, 25]]}}})
$polygon 接收多个点组成的数组,用来指定多边形。
不管是 2dsphere 索引还是 2d 索引,都可以和其他字段一起组成复合索引:
db.world.ensureIndex({"name": 1, "loc": "2dsphere"})
全文索引(Text Indexes)
全文索引用于在文档中搜索文本,我们也可以使用正则表达式来查询字符串,但是当文本块比较大的时候,正则表达式搜索会非常慢,而且无法处理语言理解的问题(如 entry 和 entries 应该算是匹配的)。使用全文索引可以非常快地进行文本搜索,就如同内置了多种语言分词机制的支持一样。创建索引的开销都比较大,全文索引的开销更大。创建索引时,需后台或离线创建。
{ "_id" : ObjectId("55a0e30427c9370e525032e9"), "content" : "This morning I had a cup of coffee.", "about" : "beverage", "keywords" : [ "coffee" ] } { "_id" : ObjectId("55a0e31027c9370e525032ea"), "content" : "Who doesn‘t like cake?", "about" : "food", "keywords" : [ "cake", "food", "dessert" ] }
文档如上所示,在 content 上创建全文索引:
db.article.ensureIndex({"content": "text"})
使用全文索引查询内容:
db.article.find({"$text": {"$search": "coffee"}})
如果要在所有字符串的键上进行文本搜索,请使用通配符 ($**) 来索引所有的包含字符串的键。创建了一个 article 中所有文档的所有键的字符串进行索引的索引,且命名为 TextIndex:
db.article.ensureIndex({"$**": "text"}, {"name": "TextIndex"})
被索引的数据的默认语言决定了如何解析词根以及忽略停止词的规则。被索引数据的默认语言是英语。如果希望指定一个不同的语言,在创建全文索引时使用 default_language 选项。
支持如下语言(不支持中文,至少在 2.6 的版本中是这样的):
- da or danish
- nl or dutch
- en or english
- fi or finnish
- fr or french
- de or german
- hu or hungarian
- it or italian
- nb or norwegian
- pt or portuguese
- ro or romanian
- ru or russian
- es or spanish
- sv or swedish
- tr or turkish
注:如果您将语言指定为值 "none" ,那么 text search 会使用简单的分词器,没有停止词也没有取词根处理。
db.quotes.ensureIndex({"content": "text"}, {"default_language": "spanish"})
哈希索引(Hashed Index)
哈希索引可以支持相等查询,但是哈希索引不支持范围查询。您可能无法创建一个带有哈希索引键的复合索引或者对哈希索引施加唯一性的限制。但是,您可以在同一个键上同时创建一个哈希索引和一个递增/递减(例如,非哈希)的索引,这样MongoDB对于范围查询就会自动使用非哈希的索引。
db.active.ensureIndex({"a": "hashed"})
上面的操作将会在 active 的 a 键上创建一个哈希索引。
索引属性
MongDB的索引属性有以下几种:TTL索引、唯一索引和稀疏索引。
TTL索引(TTL Indexes)
TTL索引是一种特殊索引,通过这种索引MongoDB会过一段时间后自动移除集合中的文档。这对于某些类型的信息来说是一个很理想的特性,例如机器生成的事件数据、日志、会话信息等,这些数据都只需要在数据库中保存有限时间。
TTL索引有如下限制:
- 它不支持复合索引 。
- 被索引键必须是日期类型的数据。
- 如果这个键存储的是一个数组,且在索引中有多个日期类型的数据(和一篇文档关联),那么当其中最低 (比如,最早)过期阀值得到匹配时,这篇文档就会过期失效了。
TTL索引不能保证过期数据会被立刻删除。在文档过期和MongoDB从数据库中删除文档之间,可能会有延迟。删除过期数据的后台任务 每隔60秒 运行一次。所以,在文档过期 之后 和 后台任务运行或者结束 之前 ,文档会依然存在于集合中。删除操作的持续实际取决于您的 mongod 实例的负载。因此,在两次后台任务运行的间隔间,过期数据可能会继续留在数据库中超过60秒。在其他方面,TTL索引是普通索引,并且如果可以的话,MongoDB会使用这些索引来匹配任意查询。
db.token.ensureIndex({"lastUpdated": 1}, {"expireAfterSecs": 60*60*24})
token 超过24小时就会被删除掉。
唯一索引(Unique Indexes)
唯一索引可以拒绝保存那些被索引键的值已经重复的文档。
db.members.ensureIndex({"user_id": 1}, {unique: true})
默认情况下,MongoDB索引的 unique 属性是 false 。如果对复合索引施加唯一性的限制,那么MongoDB就会强制要求复合值的唯一性,而不是分别对每个单独的值要求唯一。
唯一性的限制是针对一个集合中不同文档的。也即,唯一索引可以防止 不同 文档的被索引键上存储相同值,但是它不禁止同一篇文档在被索引键存储的数组里存储的元素或者内嵌文档是相同的值。在同一篇文档存储重复数据的情况下,重复的值只会被存入索引一次。
例如,一个集合有一个唯一索引 a.b :
db.collection.ensureIndex({"a.b": 1 }, {unique: true})
假如在集合中没有其他的文档的 a.b 键的值是 5 ,那么唯一索引将会允许将以下文档插入集合:
db.collection.insert({a: [{b: 5}, {b: 5}]})
如果一篇文档不包含唯一索引的被索引键,那么索引默认会为该文档存储一个null值。由于唯一性的限制,MongoDB将只允许有一篇可以不包含被索引键。如果超过一篇文档不包含被索引键或没有值,那么会抛出键重复(duplicate key)错误导致索引创建失败。可以组合使用唯一性和稀疏索引的特性来过滤那些包含null值的文档以避免这个错误。
稀疏索引(Sparse Indexes)
稀疏索引会跳过所有不包含被索引键的文档。这个索引之所以称为 “稀疏” 是因为它并不包括集合中的所有文档。与之相反,非稀疏的索引会索引每一篇文档,如果一篇文档不含被索引键则为它存储一个null值。
db.addresses.ensureIndex({"xmpp_id": 1}, {"sparse": true})
如果一个索引会导致查询或者排序的结果集是不完整的,那么MongoDB将不会使用这个索引,除非用户使用 hint() 方法来显示指定索引。例如,查询 { x: { $exists: false } } 将不会使用 x 键上的稀疏索引,除非显示的hint。
2dsphere (version 2), 2d 和 text 这些索引总是稀疏的。
只要一篇文档里有至少一个被索引键,稀疏且只包含有递增/递减索引键的复合索引就会索引这篇文档。
至于稀疏且包含有地理索引键(例如 2dsphere, 2d)以及递增/递减索引键的复合索引,只有地理索引键的存在与否能决定一篇文档是否被索引。
至于稀疏且包含了全文索引键和其他递增/递减索引键的复合索引,只有全文索引键的存在与否能决定是否索引该文档。
一个稀疏且唯一的索引,可以防止集合中的文档被索引键中出现重复值,同时也允许多个文档里不包含被索引键。