今天看MongoDB的书,有关索引部分,想起之前做项目优化数据库时好像做过索引相关的笔记,翻出来一看,还挺多。记得当时是按官方文档(3.2版)记的笔记(或者说是翻译的 (=:))。感觉还挺详细清晰,特别是最后面的索引分析那里,再去看一遍文档就有点心累了(关键官网还经常崩)
知识点
- 索引是一种特殊的数据结构,它们被存放在一个容易访问的数据表中。
- 索引存储一个字段或者几个字段的哈希值。并安装这些字段进行排序。
- 索引的这种有序特性对查找特定范围的数据和特定数据等操作特别有帮助。
{"$lt": 30}
- mongo能够根据索引的顺序返回排序好的数据
- mongo支持在任何字段建立索引,包括子文档中的字段。
- mongo索引使用的是
B-tree
树 - 索引需要进行两次查找,第一次查找索引文件,第二次再根据索引指针去查找相应的文档。这是是否需要创建索引的考虑因素之一。
索引类型
Single Field
- 对于单字段索引,mong支持升序索引(ascending)和降序索引(descending)。但在查询时升序或者降序对sort没有影响,mongo都能很从容地处理
- 字段后面的值描述了索引的类型。比如value为1时,表示该字段值按升序排列。
- 该索引对于字段值查询和字段范围查询帮助很大
- 我们也可以对内嵌文档的一个字段建立索引,甚至对整个内嵌文档。当使用内嵌索引时,在查询时请注意查询字段的顺序
当使用内嵌
//对score字段建立索引 { db.records.createIndex({score: 1}); } // 内嵌文档建立索引 { db.records.createIndex({'location.state': 1}); } // 这样的查询是有效的。 注意看顺序! db.records.find( { "location.city": "Albany", "location.state": "NY" } ) // 对整个内嵌文档建立索引 { db.records.createIndex({'location': 1}); } // 这样的查询是有效的 db.records.find( { "location.city": "Albany", "location.state": "NY" } )
Compound Index
- 联合索引建立时索引的顺序很重要。索引会先依据第一个字段排序,然后按照第二个字段排序。所以在建立联合索引时请务必考虑使用场景
- 联合索引最多只能有31个字段
考虑以下场景
db.events.createIndex( { "username" : 1, "date" : -1 } ) //支持以下两种查询 db.events.find().sort( { username: 1, date: -1 } ), db.events.find().sort( { username: -1, date: 1 } ) // 但不支持下面的这种 db.events.find().sort( { username: 1, date: 1 } ) // 当我们分别对 username和date使用单字段索引时,下面的date中的索引不起作用 db.events.find().sort( { username: 1, date: 1 } )
- 考虑这样的索引
{ "item": 1, "location": 1, "stock": 1 }
,可以当作索引的域为
item
,item, location
,item, location, stock
item, stock
也能被mongo当作索引,但它的索引效率不如直接使用二者的联合索引- mongo不能使用除了以上四者外的其他索引
- 对于上面的索引,如果item字段没有稀疏索引和唯一索引的限制,我们可以将item移除索引
- 上述也称为 “隐式索引”
Multikey Index
知识点
- 多键值索引将对 数组 中的元素建立索引。
- 当我们对一个数组建立索引时,mongo将会对数组的每个元素建立索引。
- 当我们使用文档数组元素作为查询匹配条件时。多键值索引将会起作用。
- 当我们使用文档数组值作为查询条件时,mongo会自动决定是否建立多键值索引。我们无须特别指定多键值索引类型
- 建立索引的元素类型没有限制,标量矢量均可。
限制
- 每个文档的Multikey Index最多有一个。即被索引的所以字段中最多只有一个是数组。如
{ _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" }
我们不能将 a, b 同时指定为索引 - 考虑如下情况
{ _id: 1, a: [1, 2], b: 1, category: "A array" }
{ _id: 2, a: 1, b: [1, 2], category: "B array" }
此时可以将 a, b 同时指定为索引。当当我们出入的新文档中 a,b 都是数组时,将会失败 - 当使用整个数组进行查询时,mongo只能使用multikey index去查看数组的第一个元素是否符合要求,而不能使用整个数组来匹配。获得第一个元素的匹配文档后,mongo将会把这些匹配文档返回然后再做筛选
,而不是继续使用第二个元素作为索引。
Geospatial Index
地理位置索引,能很好地解决O2O的应用场景,比如“查找附近的美食”,“查找某个区域的内的公交车”等
Text Indexes
- mongo提供text索引类型,支持对一个字符串类型的字段进行匹配索引。3.2 以上支持
- test indexes 支持值为字符串的数组值为字符串的属性。
- 对comments的子字符串“text”创建:
db.reviews.createIndex({comments: "text"})
索引性质
- 唯一性索引(Unique Indexes)
- 唯一性保证了索引值不会重复。重复的索引值将被拒绝
db.collection.createIndex( { a: 1 }, { unique: true } )
db.collection.createIndex( { a: 1, b: 1 }, { unique: true } )
部分索引(Partial Indexes)
- 局部索引:针对匹配某一条件的字符串创建索引,不匹配的不创建索引。3.2 版本支持
- 通过对文档的子集创建索引,可以减小存储需求,降低创建和维护索引对性能的消耗。
- 如下创建局部索引:
db.test.createIndex( { cuisine: 1, name: 1}, // 使用partialFilterExpression创建 {partialFilterExpression: {rating: {$gt: 5}}} // 只有rating属性值大于5的文档才会建立联合索引 )
- 要使用局部索引,则在查询中需要具备rating属性的过滤条件,如
$gte: 8
。只要最终查询结果为$gt: 5
的子集均可
- 稀疏索引(Sparse Indexes)
- 对于不存在该索引项的文档将被忽略。
- 通过 Sparse Indexes 和 Unique Indexes 的结合,我们可以使得重复索引字段不被接收,而当索引不存在时会被认定为是合法的。
db.test.createIndex({name:1}, {parse: true})
- 可看作部分索引的特殊情况
- TTL Indexes
- 该性质能自动删除文档,在一定时间后。常用于机器自动生成的事件数据文档。
索引覆盖查询(Covered Queriers)
当我们要查询的条件和返回的字段全部是索引字段时,它将会直接从索引处获取数据,而不用遍历任何文档或者将任何文档写入内存
限制(Restrictions)
- 索引值大小限制(Index Key Limit)
- 当个索引目录(index entry)必须小于1024个字节
- 如果存在文档的待索引字段超过了限制(index key limit),mongo将不会建立索引。(2.6以前的版本仍然会建立索引,区别在于不索引超过该值的文档
- 索引数量限制
当个文档不能拥有超过64个索引。 - 索引名称长度限制(Index Name Length)
<databasenam>.<collection>.$<index name>
这整个长度不得超过128个字符 - 联合索引限制数量
对于联合索引,索引包含的字段不能超过31个字段(fields)
索引创建
基本知识
- 默认情况下,当创建索引时,其他操作全部会被阻塞。所以读写操作都会被推迟到索引建立完成
- Background Construction
- 后台查询支持在索引建立过程进行其他操作,但不支持我们创建索引的那个session进行其他操作。
- 建立
db.test.createIndex({zip:1}, {background: true}
.默认情况下 background 是false。 - 效率会比较低。它使用 incremental approach。如果需要索引的文档超出了RAM,则效率会进一步降低
- 索引名称(Index Names)
- 索引名字是index key的字符连接。如
db.products.createIndex( { item: 1, quantity: -1 } )
的Index Names是item_1_quantity_-1.
。 - 我们也可以自定义索引的名称。
db.products.createIndex( { item: 1, quantity: -1 }, {name: 'inventory'} )
- 索引名字是index key的字符连接。如
交叉索引(Index Intersection)
- 假设我们有以下索引
{qty:1}, {item:1}
,对于下面的查询,mongo能够使用以上两个索引的Intersection来支持查询db.orders.find( { item: "abc123", qty: { $gt: 15 } } )
- 使用
explain()
函数,如果存在AND_SORTED或AND_HASH,则说明mongo使用了交叉索引。 - 在老版本,mongo只会使用一个索引来进行查询
- $or字句不支持这种查询,mongo将会对每个$or使用single index
考虑情景
{ qty: 1 } { status: 1, ord_date: -1 } // 对于下面的查询,交叉索引将被使用 db.orders.find( { qty: { $gt: 10 } , status: "A" } )
索引操作
db.test.getIndexes()
查询数据库所有索引
db.getCollectionNames().forEach(function(collection) { indexes = db[collecion].getIndexes(); print("Indexes for " + collection + ":"); printjson(indexes); }) // 新版mongo摒弃了直接从system.indexes表读取索引的功能
- 移除索引
db.test.dropIndex({})
,返回{"nIndexsWas":3, "ok":1}
,其中nIndexsWas
表示受影响的记录数 db.test.dropIndexes()
,删除文档所有索引- 重建索引
db.test.reIndex()
它将会删除所有索引,然后重建
索引创建策略
- 创建支持查询的索引
- 创建支持排序的索引
- 创建适应内存的索引
- Create Queries that Ensure Selectivity
索引分析
- 使用
getIndexes()
获取索引。 COLLSCAN
表示全表扫描。IXSCAN
使用索引查询explain
explain用于分析查询,提供一些查询数据。cursor.explain("executinoStats"); db.collection.explain("executionStats");
hint
hint 指明了使用什么索引。比如我们对两个字段建立联合索引,(a,b),(b,a)由于建立索引的顺序不同,所以查询利用不同的联合索引,其效果也不同,使用hint能够明确指定利用特定的索引
使用hint将有助于我们分析不同索引的查询性能。使用hint({$natural:1})
表示禁止使用索引db.test.find({quantity:{"$gte":100,$lte:300}, type:"food"}) .hint({quantity:1, type:1}) .explain("executionStats")
使用案例
// Single Indexes db.inventory.find( { quantity: { $gte: 100, $lte: 200 } } ).explain("executionStats") // 返回的数据如下 { "queryPlanner" : { "plannerVersion" : 1, ... "winningPlan" : { "stage" : "COLLSCAN", // 代表查询的方式 ... } }, "executionStats" : { "executionSuccess" : true, "nReturned" : 3, //表示匹配并返回的记录数 "executionTimeMillis" : 0, "totalKeysExamined" : 0, "totalDocsExamined" : 10, //需要访问的文档数 "executionStages" : { "stage" : "COLLSCAN", ... }, ... }, ... } // 当quantity建立索引时返回的数据如下 { "queryPlanner" : { "plannerVersion" : 1, ... "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", // IXSCAN表示索引的使用 "keyPattern" : { "quantity" : 1 }, ... } }, "rejectedPlans" : [ ] }, "executionStats" : { "executionSuccess" : true, "nReturned" : 3, "executionTimeMillis" : 0, "totalKeysExamined" : 3, //表示查询了多少个索引 "totalDocsExamined" : 3, //查询了多少个文档 "executionStages" : { ... }, ... }, ... } // Compound Indexes { "queryPlanner" : { ... "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "quantity" : 1, "type" : 1 }, ... } } }, "rejectedPlans" : [ ] }, "executionStats" : { "executionSuccess" : true, "nReturned" : 2, "executionTimeMillis" : 0, "totalKeysExamined" : 5, "totalDocsExamined" : 2, "executionStages" : { ... } }, ... }
一些总结(3.2验证)
关于过滤条件书写顺序的
- 当查询条件存在_id时,_id过滤条件会被首先使用,无论过滤条件的书写顺序如何
- 当查询条件不存在_id时,且只有一个索引,则该索引必然被使用,无论其他过滤条件是否效率更高。
- 当查询条件不存在_id,且有多个索引时,mongo的优化过程会选择最佳索引,即过滤后获取到的记录数最少。从测试返回结果来看,如果有多个索引,则多个索引都会被检查然后判断选用哪个,也就说,过滤条件存在多个索引时,条件的顺序几乎没有影响。(mongo shell上测试,3.2版本)