7索引
Mongodb Manual阅读笔记:CH2 Mongodb CRUD 操作Mongodb Manual阅读笔记:CH3 数据模型(Data Models)Mongodb Manual阅读笔记:CH4 管理Mongodb Manual阅读笔记:CH5 安全性Mongodb Manual阅读笔记:CH6 聚合Mongodb Manual阅读笔记:CH7 索引Mongodb Manual阅读笔记:CH8 复制集Mongodb Manual阅读笔记:CH9 Sharding
对于频繁使用查询,索引提供了高性能。
7.1索引介绍
索引是查询高效的解决方案。如果没有索引,那么mongodb会扫描整个collection。索引是一个b树结构,保存了collection中的部分数据。
当查询可以使用某个索引的时候,mongodb会使用索引来限制输入文档的个数。如:
创建了索引,只保证少部分的数据被扫描。
对于排序,如果有索引可用,可以不需要排序后在输出。索引本来就是有顺序的。
对于覆盖,当查询的标准和projection都在这个索引里面,查询会直接从索引中返回,不会扫描collection。
7.1.1 索引类型
Mongodb提供了不同的索引类型来支持数据查询。
7.1.1.1 默认_id
如果应用程序没有给_id指定特别的值,mongod会自动在_id 上创建索引。
_id是一个唯一索引,当相同的_id 被插入就会报错
7.1.1.2 单字段索引(Single Field)
在一个字段为key创建的索引叫做单字段索引。
7.1.1.3复合索引(Compound Index)
如果在多个字段为key那么就是复合索引,如:
7.1.1.4 Multikey Index
如果一个字段是一个数组,在这个字段上面创建索引。Mongodb会自己决定,是否要把这个索引建成Multikey Index。
7.1.1.5地理空间索引
Mongodb提供了2个地理索引,2d index,2sphere Index。
7.1.1.6文本索引
文本索引不保存指定的stop word和stem word只保留词根。
7.1.1.7 Hash索引
Hash索引时对key进行hash计算然后创建索引,目前只支持等号运行,不支持区间。
7.1.2索引属性
7.1.2.1唯一索引
索引的唯一属性,会拒绝key的重复值插入
7.1.2.2稀疏索引
稀疏属性保证了索引只包含有字段值的(对于mongo来说null也是一个值)
7.2 索引概述
本节介绍mongo中types,配置选项和索引的特性。
7.2.1索引类型
MongoDB提供了很多不同的索引类型
7.2.1.1索引类型特性
所有索引在Mongodb中都是B树结构,可以有效的支持相等的匹配和区间查询,也可以以索引顺序直接输出
索引顺序
MongoDB索引有顺序和逆序2中排序方式,对于单字段索引而言,顺序和逆序是没什么区别的。对于复合索引而言,有时候顺序转化是不行的。
索引冗余(Redundant Indexes)
一个查询只能使用一个索引,但是对于or,每个子句都可以使用其他的索引
7.2.1.2索引类型文档
本节介绍索引,单字段索引,复合索引,Multikey Indexes,地理空间索引,文本索引,Hash索引
单字段索引
Mongodb默认所有的collection都有个单字段索引,_id。
如果有一个friends collection,:
{ "_id" : ObjectID(...),
"name" : "Alice"
"age" : 27
}
可以使用一下语句在name上创建索引:
db.friends.ensureIndex( { "name" : 1 } )
案例
_id索引:_id上的索引是默认创建的,并且不能删除而且是唯一的。如果不显示的插入值,_id为ObjectID类型长度为12字节。
在子文档字段上的索引:你可以在一个子文档上创建索引,如:
{"_id": ObjectId(...)
"name": "John Doe"
"address": {
"street": "Main"
"zipcode": 53511
"state": "WI"
}
}
然后再address上创建索引:
db.people.ensureIndex( { "address.zipcode": 1 } )
在子文档上创建索引:同时也可以对整个子文档创建索引:
{
_id: ObjectId("523cba3c73a8049bcdbf6007"),
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" } } )
复合索引
当key字段大于1个的时候都被叫做复合索引。
创建方法:
db.products.ensureIndex( { "item": 1, "stock": 1 } )
对已经hash索引的字段不能创建复合索引。并且复合索引字段个数最多31个
复合索引的排序,是先对第一个字段排序,然后第二个依次类推。
查询可以使用复合索引里面的前缀,都可以使用到这个索引。
排序顺序
复合索引的本身的排序顺序会影响查询是否可以使用索引的顺序。
如索引:db.events.ensureIndex( { "username" : 1, "date" : -1 } )
db.events.find().sort( { username, date: -1 } )
db.events.find().sort( { username: -1, date: 1 } )
都可以使用这个索引的顺序,但是一下查询使用不了:
db.events.find().sort( { username: 1, date: 1 } )
Mutikey Index
Multikey索引允许MongoDB返回在数组上的查询。Mongodb自己决定是否对数组创建multikey index。
Multikey支持数组值和嵌套文档
限制
复合索引和Multikey索引的相互作用:当你创建复合索引的multikey,只能有一个字段是数组,如果建立了复合索引,在2个字段上都已经是数组,那么插入就会回绝。
Hash索引:hase索引不能被multikey索引兼容。MongoDB会折叠子文档,然后计算整个的hash值。
在嵌入文档数组中的索引:你可以使用在子文档数组上创建multikey索引。
地理空间索引和查询
手册p326
文本索引
文本索引是区分大小写的,并且可以是字符串,和字符串数组。
要使用text索引,和使用text命令要先启用text搜索。
创建文本索引:创建文本索引,如:
db.reviews.ensureIndex( { comments: "text" } )
文本索引或删除stop word和后缀。文本索引可以覆盖查询。
存储需求和性能花费:有以下几个花费和需求:
1. 文本索引修改了collection中的空间分配的方法为usePowerOf2Sizes
2. 文本索引比较大。为每个单词创建一个索引项
3. 创建文本索引和创建multikey索引很类似,会花费大量的时间
4. 在创建大的文本索引时,要确保有足够的文件描述符(Unix ulimit设置)
5. 文本索引会影响插入的吞吐量,要为每一个单词索引
6. 另外,文本索引不保存短语或者信息中近似的单词
文本查询:mongodb提供了text命令来执行文本搜索。搜索过程如下:
1.在索引创建和text命令执行的时候先标记和提取查询的term
2.为匹配到的文档分配一个分数,分数决定的文档和关键字之间的关联
默认text命令返回前100关联比较强的文档。
Hash索引
Hash索引维护了索引字段的hash值,如果是子文档,那么会对真个子文档做hash计算。Hash索引支持shard collection作为shard key。
Mongodb可以在相等匹配的时候使用hash索引,区间匹配的时候不能使用。
如果已经在这个字段上创建了hash索引,那么不能再在上面创建复合索引,但是可以创建单字段索引。
7.2.2索引属性
Mongodb除了支持多种索引之外,还支持不同的索引属性。
7.2.2.1 TTL索引
创建了TTL索引,Mongodb会根据TTL索引自动删除collection的文档。比较适用于事件数据,日志,回话信息。
这个索引有一下几个限制:
1.不支持复合索引
2.索引字段必须是时间类型
3.如果是一个数组,当数组内的时间任意一个过期,就算过期
7.2.2.2唯一索引
唯一索引会回绝所有重复的键值。
db.addresses.ensureIndex( { "user_id": 1 }, { unique: true } )
如果在复合索引上创建唯一,那么复合索引里面的key的组合是唯一的,而不是单个key唯一。
文档插入,如果没有key,那么会被当null存,如果有一个唯一索引在这个字段上,又插入一个没有key的文档,那么就会报错。你可以再加稀疏索引来过滤这些空值。
7.2.2.3稀疏索引
稀疏索引只包含了有这个字段的文档,即便这个字段是null值,当文档没有这个值的时候,就不会被保存到稀疏索引中。如:
{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }
db.scores.ensureIndex( { score: 1 } , { sparse: true } )
db.scores.find().sort( { score: -1 } )
{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }
{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }
查询只会返回有score的文档。
稀疏索引和唯一索引:稀疏索引和唯一索引一起使用可以达到,关系型数据库中建的效果(不能为空,不能重复)。
7.2.3创建索引
Mongodb创建索引通过db.collection.ensureIndex()方法,
7.2.3.1后台构建
默认创建索引都会把其他数据库操作block,对于一个运行比较长的索引创建,可以考虑后台运行。
db.people.ensureIndex( { zipcode: 1}, {background: true} )
后台创建默认为false。
特性
在2.4版本之后,mongod可以支持多个索引在后台同时创建。
后台创建之后数据库还可以正常运行,不会被堵塞,但是当前创建索引的回话会被堵塞,直到创建完成。如果后台创建了索引,那么就不能执行其他的管理操作。
性能
后台索引创建是使用递增的方式,比通常的(前台)索引创建要慢,如果索引比内存大,那么会更加大的慢。
为了避免出现性能问题,最好在数据库设计阶段就确定好索引的索引设计。
在secondary创建索引
在primary使用后台索引创建,到了secondary之后会变成前台创建。所有的索引操作在secondary中都会被block。
在secondary中创建大索引最好的方式是,重启secondary到standalone状态,然后创建索引,创建完之后再加入到复制集,然后赶上primary之间的滞后。然后在下一个secondary上创建。当所有的secondary建好之后,切换primary,然后重启变为standalone,创建索引。
当在secondary上创建索引的时候,oplog需要有足够的空间。
7.2.3.2 删除重复
当collection中有重复键,那么就无法创建唯一索引,可以使用dropDups选项强制删除索引,会报存第一个key,接下来的重复的key的文档都会被删除。
如:
db.accounts.ensureIndex( { username: 1 }, { unique: true, dropDups: true } )
默认这个选项为false
7.2.3.3 索引名
默认索引名和key,排序顺序有关,如:db.products.ensureIndex( { item, quantity: -1 } )的索引名为:item_1_quantity_-1,可以使用以下来修改指定索引名:
db.products.ensureIndex( { item: 1, quantity: -1 } , { name: "inventory" } )
7.3索引教程
本节介绍,索引创建,索引管理,地理空间索引,文本搜索,索引策略
7.3.1创建索引
7.3.1.1创建一个索引
Mongodb默认会在_id上创建一个索引,并允许用户在任意字段上创建索引
在单个字段上创建索引
可以使用ensureIndex()在单个字段上创建索引。
如:db.people.ensureIndex( { "phone-number": 1 } )
额外考虑:如果collection太大,可以考虑后台创建,让数据库处于可用状态,不会被堵塞。
7.3.1.2创建复合索引
复合索引好处,是提供了索引覆盖,可以直接从索引中返回数据。
创建复合索引:db.collection.ensureIndex( { a: 1, b: 1, c: 1 } )
额外考虑:如果collection太大,可以考虑后台创建,让数据库处于可用状态,不会被堵塞。
7.3.1.3创建唯一索引
唯一索引只是索引的一个属性
db.collection.ensureIndex( { a: 1 }, { unique: true } )
一般唯一索引和稀疏索引一起使用:db.collection.ensureIndex( { a: 1 }, { unique: true, sparse: true } )
也可以在复合索引上创建唯一属性。
删除重复:可以删除重复的key
db.collection.ensureIndex( { a: 1 }, { unique: true, dropDups: true } )
7.3.1.4创建稀疏索引
稀疏索引和非稀疏索引不同,非稀疏索引会包含所有的文档,如果没有这个字段用null填充,稀疏索引如果文档没有这个字段,那么就不会为这个文档index。
db.collection.ensureIndex( { a: 1 }, { sparse: true } )
7.3.1.5创建hash索引
Hash索引时对索引字段进行hash计算,只能用户等号的匹配,不能用于区间匹配。
db.collection.ensureIndex( { _id: "hashed" } )
考虑:hash索引可以在任何字段上创建,包括子文档,会把所有的的内容计算hash,不支持multikey。
7.3.1.6在复制集上创建索引
后台创建索引在secondary会变成前台,前台创建索引会把复制block,secondary会在primary创建了索引之后再创建,如果在shard中有复制集,那么会现在shard的primary上创建,然后再secondary上创建。
注意点
需要保证oplog有足够的空间,用来保存延迟。
过程
1.关闭一个Secondary:先关闭一个secondary然后以启动不加—replSet选项,使用不同的端口运行。
2.创建索引:使用ensure创建索引
3.重启mongod:创建完之后重启mongod加上选项—replSet修改到原来的端口。
4.在所有secondary上创建索引:每个secondary根据以上1-3步创建索引
5.在primary上创建索引二选一:
a.先在primary在上是有后台创建索引
b.然后关闭primary,让别的secondary变成primary,在通过1-3步创建索引。
在后台创建索引,会比前台创建索引时间长,并且紧凑性比较差,并且会影响primary写入性能。
7.3.1.7后台创建索引
前台索引创建会block数据,后台索引创建可以让数据库任然可用,在后台索引创建时,数据库申请读写锁不会被获取。
db.collection.ensureIndex( { a: 1 }, { background: true } )
7.3.1.8创建老式索引
手册page346
7.3.2索引管理教程
本节介绍对索引的管理:删除所有,重建索引,管理在创建的索引,返回所有索引,评估索引的使用
7.3.2.1删除所有
可以使用dropIndex()方法来删除索引,db.accounts.dropIndex( { "tax-id": 1 } )
返回{ "nIndexesWas" : 3, "ok" : 1 }
nindexesWas表示删除之前的索引个数,
可以使用db.collection.dropIndex()来删除collection下的所有索引。
7.3.2.2重建索引
使用db.collection.reIndex()方法来重建collection下的所有索引。
7.3.2.3管理在建索引
可以使用db.curentOp(),msg字段会指明是否在创建索引,进度。如果不想再创建可以使用db.killop来停止创建。
7.3.2.4返回所有索引
索引的元数据存放在system.indexex下面
获取collection下的索引
db.people.getIndexes()
获取数据库下的所有索引
db.system.indexes.find()
7.3.2.5评估索引的使用
查询性能能够很好的指明索引的使用
操作
使用explain返回执行计划:在cursor下有个explain查看查询执行计划,其中包含了使用的索引。
使用hint:使用hint来强制使用索引
db.people.find( { name: "John Doe", zipcode: { $gt: 63000 } } } ).hint( { zipcode: 1 } )
使用报表:
serverStatus的输出indexCounters,scanned,scanAndOrder
collStats的输出totalIndexSize,indexSizes
dbStats的输出dbStats.indexes,bStats.indexSize
7.3.3地理空间索引教程
手册p349
7.3.4文本查询教程
本节介绍,启动文本查询,创建文本索引,查询文本,指定语言,为文本索引创建名称,使用权重控制查询结果,限制没扫描的文档,创建覆盖索引
7.3.4.1启动文本查询
文本查询现在还是beta版本,一下特性:
1.需要先启动文本查询
2.如果要为shard或者复制集启动文本查询,要在每个mongod中都启动
mongod --setParameter textSearchEnabled=true
也可以在配置文件中设置textSearchEnable。
7.3.4.2创建文本索引
可以在多个包含字符串或者字符串数组上面创建文本索引
指定字段
db.collection.ensureIndex(
{
subject: "text",
content: "text"
})
为所有字段创建索引
为所有是字符串的字段创建索引可以使用,通配符($**)
db.collection.ensureIndex(
{ "$**": "text" },
{ name: "TextIndex" }
)
7.3.4.3查询文本
按组查询(Search For a Term)
db.quotes.runCommand( "text", { search: "TOMORROW" } )
文本是大小写敏感的,使用text命令查询。
匹配任意一个查询组
db.quotes.runCommand( "text", { search: "tomorrow largo" } )
查询包含tomorrow或者largo的文档
短语匹配
db.quotes.runCommand( "text", { search: "\"and tomorrow\"" } )
这个可以用来匹配and tomorrow短语
查询的时候有短语和独立的组时,短语和组之间使用and,组和组之间使用or
db.quotes.runCommand( "text", { search: "\"and tomorrow\"" } )
类似于(corto OR largo OR tomorrow) AND ("and tomorrow")
Tomorrow来至于短语。
匹配非某个单词之外的
db.quotes.runCommand( "text" , { search: "tomorrow -petty" } )
匹配tomorrow,但是不包含petty
限制匹配的结果集
默认text命令会返回100个文档,可以使用limit来限制返回
db.quotes.runCommand( "text", { search: "tomorrow", limit: 2 } )
指定返回结果集的列
Text命令中可以使用project来控制返回的列,1返回,0不返回
db.quotes.runCommand( "text", { search: "tomorrow",
project: { "src": 1 } } )
使用其他的查询条件过滤
Text命令的filter选项提供了这个功能。之间是and关系
db.quotes.runCommand( "text", { search: "tomorrow",
filter: { speaker : "macbeth" } } )
指定特定的语言查询
语言决定了stop word和stem word
db.quotes.runCommand( "text", { search: "amor", language: "spanish" } )
文本查询的输出
文本查询的结果以文档的方式输出
7.3.4.4指定语言
为文本索引指定默认语言
如果不填默认语言,语言为英语。如果要指定不同的语言可以在创建索引的时候指定。
db.collection.ensureIndex(
{ content : "text" },
{ default_language: "spanish" }
)
创建多语言的文本索引
在文档中指定语言:
1.如果文档保存一个language的字段,默认创建索引的时候会以这个字段为语言,来覆盖默认的英文
2.如果语言字段没有language字段,而是别的字段,那么在索引创建的时候,使用language_override指向这个字段。
包含language字段:
{ _id: 1, language: "portuguese", quote: "A sorte protege os audazes" }
{ _id: 2, language: "spanish", quote: "Nada hay más surreal que la realidad." }
{ _id: 3, language: "english", quote: "is this a dagger which I see before me" }
db.quotes.ensureIndex( { quote: "text" } )
如果文档里面有language,创建索引使用这个语言
如果没有,则使用英文。
db.quotes.runCommand( "text", { search: "que", language: "spanish" } )
因为在西班牙语上面que是stop word 所以匹配不到任何东西。
指定一个字段作为语言:
可以使用language_override选项上指定字段作为语言
{ _id: 1, idioma: "portuguese", quote: "A sorte protege os audazes" }
{ _id: 2, idioma: "spanish", quote: "Nada hay más surreal que la realidad." }
{ _id: 3, idioma: "english", quote: "is this a dagger which I see before me" }
db.quotes.ensureIndex( { quote : "text" },
{ language_override: "idioma" } )
1.如果包含idioma字段,那么以这个字段里面的语言作为语言
2.如果没有用英语
7.3.4.5为文本索引创建名字
默认文本索引创建好后,mongo会自动为这个索引创建一个名字
db.collection.ensureIndex(
{
content: "text",
"users.comments": "text",
"users.profiles": "text"
}
)
"content_text_users.comments_text_users.profiles_text"
当然也可以指定索引名
db.collection.ensureIndex(
{content: "text",
"users.comments": "text",
"users.profiles": "text"
},
{name: "MyTextIndex"}
)
7.3.4.6使用权重控制查询
默认text命令根据分数(scores)从高到低来匹配文档。对于文本索引来说,权重就表示这个字段和其他字段对于查询组来说的重要性。
默认所有的索引字段的权重都是1,可以在创建索引的时候调整
db.blog.ensureIndex(
{
content: "text",
keywords: "text",
about: "text"
},
{
weights: {
content: 10,
keywords: 5,
},
name: "TextIndex"
}
)
7.3.4.7限制扫描行数
就是用索引来现在扫描行数
{ _id: 1, dept: "tech", description: "a fun green computer" }
{ _id: 2, dept: "tech", description: "a wireless red mouse" }
{ _id: 3, dept: "kitchen", description: "a green placemat" }
{ _id: 4, dept: "kitchen", description: "a red peeler" }
{ _id: 5, dept: "food", description: "a green apple" }
{ _id: 6, dept: "food", description: "a red potato" }
db.inventory.runCommand( "text", {
earch: "green",
filter: { dept : "kitchen" }
})
db.inventory.ensureIndex({
dept: 1,
description: "text"
})
因为会扫描过滤特定的字段kitchen,然后创建一个符合索引把dept放在前面。
1.排序索引必须在文本索引的前面
2.只会对符合prefix的进行索引
3.不能再有multikey索引或者地理空间索引
4.text命令必须要办filter,并且使用等号条件。
这样的话指定的dept会限制扫描的行数
7.3.4.8创建文本覆盖索引
1.添加text到排序索引中
2.使用text命令的时候使用project限制字段。
db.collection.ensureIndex( { comments: "text",username: 1 } )
db.quotes.runCommand( "text", { search: "tomorrow",project: { username: 1,_id: 0}})
7.3.5索引策略
当设计索引的时候,要考虑读写的比例,内存,查询的类型。
在创建索引的时候,要知道所有的查询,虽然索引有性能的消耗,但是对查询的效果是很明显的。
要验证索引是否还有用,哪些运行的最好,如果这个索引没用了,那么请干掉。
7.3.5.1创建索引支持查询
当所有覆盖的时候mongo获取扫描索引,而不是从collection中要数据。
如果所有的查询使用相同的单个key创建单字段索引
如果你的索引只对一个key进行查询那么创建一个单字段索引。
为不同的查询创建复合索引
复合索引有多个key组成,主要复合prefix,都可以使用到复合索引。
创建覆盖索引
覆盖索引是为了让mongo不再去扫描collection中的数据
1.所有的查询中的字段都要在索引中
2.所有结果返回的字段也要在索引中
这样效率比较高,因为索引要不再内存中,要不就是在磁盘中但是是顺序的。
使用explain()中的indexOnly如果为true那么就是覆盖的,否则就不是
7.3.5.2为排序的查询提供顺序
可以使用索引里面的排序,为查询提供了很好的性能。
如果索引时一个覆盖索引,并且查询时索引的prefix,并且前面是使用相等的匹配,那么就可以使用索引的排序。或者排序时覆盖索引的prefix,也可以使用索引的排序。
explain()返回中如果scanAndOrder为false说明可以使用索引顺序
7.3.5.3保证索引都在内存中
使用db.collection.totalIndexSize()函数查看索引大小,保证加上workset之后比物理内存小。如果有多个collection,那么要看所有的索引大小和workset都可以同时在内存中。
只保存当前数据在内存中
有些索引时不需要全部都在内存中的,只要保证最常用的在内存中的就可以了。
7.3.5.4保证查询的选择性
选择性是使用索引限制返回行数的能力,低选择性的索引对查询本身就没有什么很大的好处。可以让多个低选择性的字段组合成复合索引,来提高选择性,或者低选择性的字段和高选择性的字段组合成复合索引。
低选择性可能比查询整个collection还要慢。
7.4索引指南
手册p374