MongoDB 索引
MongoDB索引简述
索引是一种用来快速查询数据的数据结构。
如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。 这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对系统的性能是非常致命的。索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构;
MongoDB 采用 B+Tree 做索引,索引创建在 colletions 上。
索引操作
索引创建
db.collection.createIndex(keys, options)
Key 值为你要创建的索引字段,1 按升序创建索引, -1 按降序创建索引
可选参数列表如下:
# 创建索引, 默认索引名为title_1
db.book.createIndex({title:1})
# 创建唯一索引
db.book.createIndex({title:1},{unique:true})
# 创建复合索引
db.products.createIndex(
{ title: 1, favCount: -1 } ,
{ name: "title_favCount" }
)
查看索引
// 查看索引信息
db.collection.getIndexes()
// 查看索引键
db.collection.getIndexKeys()
// 查看索引占用空间。is_detail为0则只显示所有索引的总大小,为1显示每个索引的大小及总大小
db.collection.totalIndexSize(is_detail)
删除索引
// 删除集合指定索引
db.collection.dropIndex("索引名称")
// 删除集合所有索引
db.collection.dropIndexes()
索引类型
单键索引 (Single Field Indexes)
//在某一个特定的字段上建立了唯一的单键索引
db.book2.createIndex({title:1})
复合索引 (Compound Index)
//复合索引是多个字段组合而成的索引
db.book2.createIndex({type:1,favCount:1})
多键索引 (Multikey Index)
准备 inventory 集合:
db.inventory.insertMany([
{ _id: 1, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] },
{ _id: 2, type: "food", item: "bbb", ratings: [ 5, 9 ] },
{ _id: 3, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] },
{ _id: 4, type: "food", item: "ddd", ratings: [ 9, 5 ] },
{ _id: 5, type: "food", item: "eee", ratings: [ 5, 9, 5 ] }
])
创建多键索引:
//在数组的属性上建立索引。针对该数组的任意值都会定位到该文档,既多个索引入口或者键值
db.inventory.createIndex( { ratings: 1 } )
查询:
> db.inventory.find({ratings:{$in:[8]}})
{ "_id" : 1, "type" : "food", "item" : "aaa", "ratings" : [ 5, 8, 9 ] }
{ "_id" : 3, "type" : "food", "item" : "ccc", "ratings" : [ 9, 5, 8 ] }
创建复合多键索引:
db.inventory.createIndex( { item:1,ratings: 1} )
地理空间索引 (Geospatial Index)
假设商家的数据模型如下:
db.restaurant.insert({
restaurantId: 0,
restaurantName:"兰州牛肉面",
location : {
type: "Point",
coordinates: [ -73.97, 40.77 ]
}
})
创建一个 2dsphere 索引:
db.restaurant.createIndex({location : "2dsphere"})
查询附近10000米商家信息:
db.restaurant.find( {
location:{
$near :{
$geometry :{
type : "Point" ,
coordinates : [ -73.88, 40.78 ]
} ,
$maxDistance:10000
}
}
} )
$near 查询操作符,用于实现附近商家的检索,返回数据结果会按距离排序。
$geometry 操作符用于指定一个 GeoJSON 格式的地理空间对象。type=Point 表示地理坐标点,coordinates 则是用户当前所在的经纬度位置;
$maxDistance 限定了最大距离,单位是米。
全文索引 (Text Indexes)
数据准备:
db.stores.insert([
{ _id: 1, name: "Java Hut", description: "Coffee and cakes" },
{ _id: 2, name: "Burger Buns", description: "Gourmet hamburgers" },
{ _id: 3, name: "Coffee Shop", description: "Just coffee" },
{ _id: 4, name: "Clothes Clothes Clothes", description: "Discount clothing"},
{ _id: 5, name: "Java Shopping", description: "Indonesian goods" }
])
创建索引:
//通过建立文本索引来实现简易的分词检索。$text 操作符可以执行文本检索。
db.stores.createIndex( { description: "text" } )
查询:
> db.stores.find({$text: {$search: "java coffee shop"}})
{ "_id" : 3, "name" : "Coffee Shop", "description" : "Just coffee" }
{ "_id" : 1, "name" : "Java Hut", "description" : "Coffee and cakes" }
Hash索引 (Hashed Indexes)
不同于传统的 B-Tree 索引,哈希索引使用 hash 函数来创建索引。在索引字段上进行精确匹配,但不支持范围查询,不支持多键 hash; Hash 索引上的入口是均匀分布的,在分片集合中非常有用。
db.users.createIndex({username : 'hashed'})
通配符索引 (Wildcard Indexes)
MongoDB 的文档模式是动态变化的,而通配符索引可以建立在一些不可预知的字段上,以此实现查询的加速。MongoDB4.2 引入了通配符索引来支持对未知或任意字段的查询。
准备商品数据,不同商品属性不一样:
db.products.insert([
{
"_id" : 1,
"product_name" : "Spy Coat",
"product_attributes" : {
"material" : [ "Tweed", "Wool", "Leather" ],
"size" : { "length" : 72, "units" : "inches"}
}
},
{
"_id" : 2,
"product_name" : "Spy Pen",
"product_attributes" : {
"colors" : [ "Blue", "Black" ],
"secret_feature" : { "name" : "laser", "power" : "1000", "units" : "watts"}
}
},
{
"_id" : 3,
"product_name" : "Spy Book"
}
])
创建通配符索引:
db.products.createIndex( { "product_attributes.$**" : 1 } )
查询:通配符索引可以支持任意单字段查询 product_attributes 或其嵌入字段
db.products.find( { "product_attributes.size.length" : { $gt : 60 } } )
db.products.find( { "product_attributes.material" : "Leather" } )
db.products.find( { "product_attributes.secret_feature.name" : "laser" } )
- 通配符索引不兼容的索引类型或属性。(Compound、TTL、Text、2d、2dsphere、Hashed、Unique)
- 通配符索引是稀疏的,不索引空字段。因此,通配符索引不能支持查询字段不存在的文档。
- 通配符索引为文档或数组的内容生成条目,而不是文档/数组本身。因此通配符索引不能支持精确的文档/数组相等匹配。通配符索引可以支持查询字段等于空文档 {} 的情况。
如,不支持 db.products.find({ "product_attributes.colors" : [ "Blue", "Black" ] } )
索引属性
唯一索引 (Unique Indexes)
通过建立唯一性索引,可以保证集合中文档的指定字段拥有唯一值。
// 创建唯一索引
db.book2.createIndex({title:1},{unique:true})
// 复合索引支持唯一性约束
db.book2.createIndex({title:1, type:1},{unique:true})
// 多键索引支持唯一性约束
db.inventory.createIndex({ratings:1},{unique:true})
部分索引 (Partial Indexes)
部分索引仅对满足指定过滤器表达式的文档进行索引。
db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)
查询:
// 符合条件,使用索引
db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } )
// 不符合条件,不能使用索引
db.restaurants.find( { cuisine: "Italian" } )
注意:唯一约束结合部分索引使用导致唯一约束失效的问题。
// 只有满足筛选器表达式的文档,才满足唯一约束。
db.users.createIndex(
{ username: 1 },
{ unique: true, partialFilterExpression: { age: { $gte: 21 } } }
)
稀疏索引 (Sparse Indexes)
索引的稀疏属性确保索引只包含具有索引字段的文档的条目,索引将跳过没有索引字段的文档。
# 只对包含xmpp_id字段的文档进行索引
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
注意:稀疏索引会导致查询和排序操作的结果集不完整。
db.scores.insertMany([
{"userid" : "newbie"},
{"userid" : "abby", "score" : 82},
{"userid" : "nina", "score" : 90}
])
db.scores.createIndex( { score: 1 } , { sparse: true } )
// 使用稀疏索引
db.scores.find( { score: { $lt: 90 } } )
// 不使用稀疏索引,以返回完整的结果
db.scores.find().sort( { score: -1 } )
// 想使用稀疏索引,使用hint()显式指定索引, 但结果集不完整
db.scores.find().sort( { score: -1 } ).hint( { score: 1 } )
TTL索引 (TTL Indexes)
TTL索引需要声明在一个日期类型的字段中,TTL 索引是特殊的单字段索引,MongoDB 可以使用它在一定时间或特定时钟时间后自动从集合中删除文档。
db.log_events.insertOne( {
"createDate": new Date(),
"logMessage": "Success!"
} )
创建TTL索引:
db.log_events.createIndex( { "createDate": 1 }, { expireAfterSeconds: 20 } )
修改过期时间:
db.runCommand({
collMod:"log_events",
index:{keyPattern:{createDate:1},expireAfterSeconds:600}
})
:::tip
TTL 索引只能支持单个字段,并且必须是非 _id 字段。
TTL 索引不能用于固定集合。
TTL 索引无法保证及时的数据清理,MongoDB 会通过后台的 TTLMonitor 定时器来清理老化数据,默认的间隔时间是1分钟。当然如果在数据库负载过高的情况下,TTL 的行为则会进一步受到影响。
TTL 索引对于数据的清理仅仅使用了
remove
命令,这种方式并不是很高效。因此TTL Monitor在运行期间对系统CPU、磁盘都会造成一定的压力。相比之下,按日期分表的方式操作会更加高效。 :::
隐藏索引 (Hidden Indexes)
隐藏索引对查询规划器不可见,不能用于支持查询。通过对规划器隐藏索引,用户可以在不实际删除索引的情况下评估删除索引的潜在影响。如果影响是负面的,用户可以取消隐藏索引,而不必重新创建已删除的索引。4.4新版功能。
// 创建隐藏索引
db.restaurants.createIndex({ borough: 1 },{ hidden: true });
// 隐藏现有索引
db.restaurants.hideIndex( { borough: 1} );
db.restaurants.hideIndex( "索引名称" )
// 取消隐藏索引
db.restaurants.unhideIndex( { borough: 1} );
db.restaurants.unhideIndex( "索引名称" );
explain 执行计划
db.collection.find().explain(<verbose>)
stage 状态:
执行计划的返回结果中尽量不要出现以下 stage:
- COLLSCAN (全表扫描)
- SORT (使用sort但是无index)
- 不合理的 SKIP
- SUBPLA (未用到index的$or)
- COUNTSCAN (不使用index进行count)
注意事项
既然索引可以加快查询速度,那么是不是只要是查询语句,就创建索引呢?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,数据库在运行时也要消耗资源维护索引,因此索引并不是越多越好。 那么什么情况不建议创建索引呢? 例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全集合扫描就好了。 至于多少条记录才算多?以万为单位来做索引;
如何创建合适的索引
为每一个常用查询结构建立合适的索引。 复合索引是创建的索引由多个字段组成, 例如: db.test.createIndex({“username”:1, “age”:-1}) 交叉索引是每个字段单独建立索引,但是在查询的时候组合查找, 例如: db.test.createIndex({“username”:1}) db.test.createIndex({“age”:-1}) db.test.find({“username”:“kaka”, “age”: 30})
交叉索引的查询效率较低,在使用时,当查询使用到多个字段的时候,尽量使用复合索引,而不是交叉索引。
复合索引的字段排列顺序
当我们的组合索引内容包含匹配条件以及范围条件的时候,比如包含用户名(匹配条件)以及年龄(范围条件),那么匹配条件应该放在范围条件之前。
比如需要查询: db.test.find({“username”:“kaka”, “age”: {$gt: 30}}) 那么复合索引应该这样创建: db.test.ensureIndex({“username”:1, “age”:-1})
查询时尽可能仅查询出索引字段
有时候仅需要查询少部分的字段内容,而且这部分内容刚好都建立了索引,那么尽可能只查询出这些索引内容,需要用到的字段显式声明(_id字段需要显式忽略!)。因为这些数据需要把原始数据文档从磁盘读入内存,造成一定的损耗。
比如说我们的表有三个字段: name, age, mobile 索引是这样建立的: db.stu.createIndex({“name”:1,“age”:-1}) 我们仅需要查到某个用户的年龄(age),那可以这样写: db.stu.find({“name”:“kaka”}, {“_id”:0, “age”:1}) 注意到上面的语句,我们除了”age”:1外,还加了”_id”:0,因为默认情况下,_id都是会被一并查询出来的,当不需要_id的时候记得直接忽略,避免不必要的磁盘操作。
对现有的数据大表建立索引的时候,采用后台运行方式
在对数据集合建立索引的过程中,数据库会停止该集合的所有读写操作,因此如果建立索引的数据量大,建立过程慢的情况下,建议采用后台运行的方式,避免影响正常业务流程。 db.stu.ensureIndex({“name”:1,“age”:-1},{“background”:true})
索引限制
额外开销
每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。反之:使用索引的属性一定查询次数远远高于增加、删除、修改次数。
内存使用
由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。 如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
查询限制
索引不能被以下的查询使用: 正则表达式(最左匹配除外)及非操作符,如 $nin, $not, 等。 算术运算符,如 $mod, 等。 所以,检测你的语句是否使用索引是一个好的习惯,可以用explain来查看。
最大范围
集合中索引不能超过64个 索引名的长度不能超过128个字符 一个复合索引最多可以有31个字段
- 感谢你赐予我前进的力量