查询优化器html
什么时候查询计划缓存才会变呢java
联合索引的优化mongodb
聚合管道的优化数据库
最指望看到的查询组合json
最不指望看到的查询组合数组
最左前缀原则缓存
效率极低的操做符并发
MongoDB 支持文档集合中任何字段的索引,在默认状况下,全部集合在 _id 字段上都有一个索引,应用程序和用户能够添加额外的索引来支持重要的查询操做app
对于单字段索引和排序操做,索引键的排序顺序(即升序或降序)可有可无,由于 MongoDB 能够在任意方向上遍历索引。运维
建立单键索引的语法结构以下:
# 1 为升序,-1 为降序 db.collection.createlndex ( { key: 1 } )
如下示例为插入一个文档,并在 score 键上建立索引,具体步骤以下:
db.records.insert( { "score" : 1034, "location" : { state: "NY", city: "New York"} } ) db.records.createTndex( { score: 1 } )
使用 score 字段进行查询,再使用 explain() 函数,能够查看查询过程:
db.records.find({score:1034}).explain()
{ "address": { "city": "Los Angeles", "state": "California", "pincode": "123" }, "tags": [ "music", "cricket", "blogs" ], "name": "Tom Benzamin" }
假设咱们须要经过city、state、pincode字段来检索文档,因为这些字段是子文档的字段,因此咱们须要对子文档创建索引。
为子文档的city字段建立索引,命令以下:
db.users.ensureIndex({"address.city":1})
对嵌套文档自己“address”创建索引,与对嵌套文档的某个字段(address.city)创建索引是彻底不相同的。
对整个文档创建索引,只有在使用文档完整匹配时才会使用到这个索引,例如创建了这样一个索引db.personInfos.createIndex({“address”:1}),那么只有使用db.personInfos.find({“address”:{“pincode”:”xxx”,”city”:”xxx”,""state":"xxx"}})这种完整匹配时才会使用到这个索引,使用db.personInfos.find({“address.city”:”xxx”})是不会使用到该索引的。
惟一索引是索引具备的一种属性,让索引具有惟一性,确保这张表中,该条索引数据不会重复出现。在每一次insert和update操做时,都会进行索引的惟一性校验,保证该索引的字段组合在表中惟一。
db.containers.createIndex({name: 1},{unique:true, background: true}) db.packages.createIndex({ appId: 1, version: 1 },{unique:true, background: true})
Mongo提供两种建索引的方式foreground和background。
前台操做,它会阻塞用户对数据的读写操做直到index构建完毕;
后台模式,不阻塞数据读写操做,独立的后台线程异步构建索引,此时仍然容许对数据的读写操做。
建立索引时必定要写{background: true}
建立索引时必定要写{background: true}
建立索引时必定要写{background: true}MongoDB中是只有库级锁的,建立索引时要添加参数{background: true}。
MongoDB 支持复合索引,其中复合索引结构包含多个字段
复合索引能够支持在多个字段上进行的匹配查询,语法结构以下:
db.collection.createIndex ({ <key1> : <type>, <key2> : <type2>, ...})
须要注意的是,在创建复合索引的时候必定要注意顺序的问题,顺序不一样将致使查询的结果也不相同。
以下语句建立复合索引:
db.records.createIndex ({ "score": 1, "location.state": 1 })
查看复合索引的查询计划的语法以下:
db.records.find({score:1034, "location.state" : "NY"}).explain()
若要为包含数组的字段创建索引,MongoDB 会为数组中的每一个元素建立索引键。这些多键值索引支持对数组字段的高效查询
建多键值索引的语法以下:
db.collecttion.createlndex( { <key>: < 1 or -1 > })
须要注意的是,若是集合中包含多个待索引字段是数组,则没法建立复合多键索引。
如下示例代码展现插入文档,并建立多键值索引:
db.survey.insert ({item : "ABC", ratings: [ 2, 5, 9 ]}) db.survey.createIndex({ratings:1}) db.survey.find({ratings:2}).explain()
对数组创建索引的代价是很是高的,他其实是会对数组中的每一项都单独创建索引,就至关于假设数组中有十项,那么就会在原基础上,多出十倍的索引大小。若是有一百个一千个呢?
因此在mongo中是禁止对两个数组添加复合索引的,对两个数组添加索引那么索引大小将是爆炸增加,因此谨记在心。
能够针对某个时间字段,指定文档的过时时间(通过指定时间后过时 或 在某个时间点过时)
是指按照某个字段的hash值来创建索引,hash索引只能知足字段彻底匹配的查询,不能知足范围查询等
能很好的解决一些场景,好比『查找附近的美食』、『查找附近的加油站』等
能解决快速文本查找的需求,好比,日志平台,相对日志关键词查找,若是经过正则来查找的话效率极低,这时就能够经过文本索引的形式来进行查找
若要返回集合上全部索引的列表,则需使用驱动程序的 db.collection.getlndexes() 方法或相似方法。
例如,可以使用以下方法查看 records 集合上的全部索引:
db.records.getIndexes()
若要列出数据库中全部集合的全部索引,则需在 MongoDB 的 Shell 客户端中进行如下操做:
db.getCollectionNames().forEach(function(collection){ indexes = db[collection].getIndexes(); print("Indexes for " + collection + ":" ); printjson(indexes); });
MongoDB 提供的两种从集合中删除索引的方法以下:
# 删除单个索引 db.collection.dropIndex("") # 删除集合的所有索引 db.collection.dropIndexes()
若要删除特定索引,则可以使用该 db.collection.droplndex() 方法。
例如,如下操做将删除集合中 score 字段的升序索引:
db.records.dropIndex ({ "score" : 1 }) //升序降序不能错,若是为-1,则提示无索引
还可使用 db.collection.droplndexes() 删除除 _id 索引以外的全部索引。
例如,如下命令将从 records 集合中删除全部索引:
db.records.dropIndexes()
db.myCollection.reIndex() db.runCommand( { reIndex : 'myCollection' } )
一般这是没必要要的,可是在集合的大小变更很大及集合在磁盘空间上占用不少空间时重建索引才有用。对于大数据量的集合来讲,重建索引可能会很慢。
参数 | 类型 | 描述 |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操做,background可指定之后台方式建立索引,即增长 "background" 可选参数。 "background" 默认值为false。 |
unique | Boolean | 创建的索引是否惟一。指定为true建立惟一索引。默认值为false. |
name | string | 索引的名称。若是未指定,MongoDB的经过链接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | 3.0+版本已废弃。在创建惟一索引时是否删除重复记录,指定 true 建立惟一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数须要特别注意,若是设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod建立索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其余索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
Mongo自带了一个查询优化器会为咱们选择最合适的查询方案。
若是一个索引可以精确匹配一个查询,那么查询优化器就会使用这个索引。
若是不能精确匹配呢?可能会有几个索引都适合你的查询,那MongoDB是怎样选择的呢?
当你查询条件的顺序和你索引的顺序不一致的话,mongo会自动的调整查询顺序,保证你可使用上索引。
例如:你的查询条件是(a,c,b)可是你的索引是(a,b,c)mongo会自动将你的查询条件调整为abc,寻找最优解。
然而管道中的索引使用状况是极其不佳的,在管道中,只有在管道最开始时的match sort可使用到索引,一旦发生过project投射,group分组,lookup表关联,unwind打散等操做后,就彻底没法使用索引。
- Fetch+IDHACK
- Fetch+ixscan
- Limit+(Fetch+ixscan)
- PROJECTION+ixscan
- COLLSCAN(全表扫)
- SORT(使用sort可是无index)
- COUNTSCAN****(不使用索引进行count)
假定索引(a,b,c) 它可能知足的查询以下:
1. a
2. a,b
3. a,b,c
4. a,c [该组合只能用a部分]
5. a, c, b [cb在查询时会被优化换位置]
显然,最左前缀的核心是查询条件字段必须含有索引第一个字段
最左值尽量用最精确过滤性最好的值,不要用那种可能会用于范围模糊查询,用于排序的字段
- \(where和\)exists:这两个操做符,彻底不能使用索引。
- \(ne和\)not:一般来讲取反和不等于,可使用索引,可是效率极低,不是颇有效,每每也会退化成扫描全表。
- $nin:不包含,这个操做符也老是会全表扫描
- 对于管道中的索引,也很容易出现意外,只有在管道最开始时的match sort可使用到索引,一旦发生过project投射,group分组,lookup表关联,unwind打散等操做后,就彻底没法使用索引。
执行explain
db.union_recipe.find({"name" : /.*鸡.*/i,"foodTags.text":"鲁菜"}).explain("executionStats")
查询出来的计划
{ "queryPlanner": { "plannerVersion": NumberInt("1"), "namespace": "iof_prod_recipe.union_recipe", "indexFilterSet": false, "parsedQuery": { "$and": [ { "foodTags.text": { "$eq": "鲁菜" } }, { "name": { "$regex": ".*鸡.*", "$options": "i" } } ] }, "winningPlan": { # 根据内层阶段树查到的索引去抓取完整的文档 "stage": "FETCH", "filter": { "name": { "$regex": ".*鸡.*", "$options": "i" } }, # 每一个阶段将本身的查询结果传递给父阶段树,因此从里往外读Explain "inputStage": { # IXSCAN该阶段使用了索引进行扫描 "stage": "IXSCAN", # 使用了 foodTags.text: -1 这条索引 "keyPattern": { "foodTags.text": -1 }, "indexName": "foodTags.text_-1", "isMultiKey": true, "multiKeyPaths": { "foodTags.text": [ "foodTags" ] }, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": NumberInt("2"), "direction": "forward", "indexBounds": { "foodTags.text": [ "[\"鲁菜\", \"鲁菜\"]" ] } } }, "rejectedPlans": [ { "stage": "FETCH", "filter": { "foodTags.text": { "$eq": "鲁菜" } }, "inputStage": { "stage": "IXSCAN", "filter": { "name": { "$regex": ".*鸡.*", "$options": "i" } }, "keyPattern": { "name": 1 }, "indexName": "name_1", "isMultiKey": false, "multiKeyPaths": { "name": [ ] }, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": NumberInt("2"), "direction": "forward", "indexBounds": { "name": [ "[\"\", {})", "[/.*鸡.*/i, /.*鸡.*/i]" ] } } } ] }, "executionStats": { "executionSuccess": true, "nReturned": NumberInt("49"), "executionTimeMillis": NumberInt("2"), "totalKeysExamined": NumberInt("300"), "totalDocsExamined": NumberInt("300"), "executionStages": { "stage": "FETCH", "filter": { "name": { "$regex": ".*鸡.*", "$options": "i" } }, "nReturned": NumberInt("49"), "executionTimeMillisEstimate": NumberInt("0"), "works": NumberInt("302"), "advanced": NumberInt("49"), "needTime": NumberInt("251"), "needYield": NumberInt("0"), "saveState": NumberInt("5"), "restoreState": NumberInt("5"), "isEOF": NumberInt("1"), "invalidates": NumberInt("0"), "docsExamined": NumberInt("300"), "alreadyHasObj": NumberInt("0"), "inputStage": { "stage": "IXSCAN", "nReturned": NumberInt("300"), "executionTimeMillisEstimate": NumberInt("0"), "works": NumberInt("301"), "advanced": NumberInt("300"), "needTime": NumberInt("0"), "needYield": NumberInt("0"), "saveState": NumberInt("5"), "restoreState": NumberInt("5"), "isEOF": NumberInt("1"), "invalidates": NumberInt("0"), "keyPattern": { "foodTags.text": -1 }, "indexName": "foodTags.text_-1", "isMultiKey": true, "multiKeyPaths": { "foodTags.text": [ "foodTags" ] }, "isUnique": false, "isSparse": false, "isPartial": false, "indexVersion": NumberInt("2"), "direction": "forward", "indexBounds": { "foodTags.text": [ "[\"鲁菜\", \"鲁菜\"]" ] }, "keysExamined": NumberInt("300"), "seeks": NumberInt("1"), "dupsTested": NumberInt("300"), "dupsDropped": NumberInt("0"), "seenInvalidated": NumberInt("0") } } }, "ok": 1, "operationTime": Timestamp(1598602456, 1), "$clusterTime": { "clusterTime": Timestamp(1598602456, 1), "signature": { "hash": BinData(0, "/t+ZhDHuT6EtZMFyqmesvq9Rlfk="), "keyId": NumberLong("6838110804550615041") } } }
queryPlanner:查询计划的选择器,首先进行查询分析,最终选择一个winningPlan,是explain返回的默认层面。
executionStats:为执行统计层面,返回winningPlan的统计结果
allPlansExecution:为返回全部执行计划的统计,包括rejectedPlan
因此:咱们在查询优化的时候,只须要关注queryPlanner, executionStats便可,由于queryPlanner为咱们选择出了winningPlan, 而executionStats为咱们统计了winningPlan的全部关键数据。
explain.queryPlanner: queryPlanner的返回 explain.queryPlanner.namespace:该值返回的是该query所查询的表 explain.queryPlanner.indexFilterSet:针对该query是否有indexfilter explain.queryPlanner.winningPlan:查询优化器针对该query所返回的最优执行计划的详细内容。 explain.queryPlanner.winningPlan.stage:最优执行计划的stage,这里返回是FETCH,能够理解为经过返回的index位置去检索具体的文档(stage有数个模式,将在后文中进行详解)。 Explain.queryPlanner.winningPlan.inputStage:用来描述子stage,而且为其父stage提供文档和索引关键字。 explain.queryPlanner.winningPlan.stage的child stage,此处是IXSCAN,表示进行的是index scanning。 explain.queryPlanner.winningPlan.keyPattern:所扫描的index内容,此处是did:1,status:1,modify_time: -1与scid : 1 explain.queryPlanner.winningPlan.indexName:winning plan所选用的index。 explain.queryPlanner.winningPlan.isMultiKey是不是Multikey,此处返回是false,若是索引创建在array上,此处将是true。 explain.queryPlanner.winningPlan.direction:此query的查询顺序,此处是forward,若是用了.sort({modify_time:-1})将显示backward。 explain.queryPlanner.winningPlan.indexBounds:winningplan所扫描的索引范围,若是没有制定范围就是[MaxKey, MinKey],这主要是直接定位到mongodb的chunck中去查找数据,加快数据读取。 explain.queryPlanner.rejectedPlans:其余执行计划(非最优而被查询优化器reject的)的详细返回,其中具体信息与winningPlan的返回中意义相同,故不在此赘述。
executionStats.executionSuccess:是否执行成功 executionStats.nReturned:知足查询条件的文档个数,即查询的返回条数 executionStats.executionTimeMillis:总体执行时间 executionStats.totalKeysExamined:索引总体扫描的文档个数,和早起版本的nscanned 是同样的 executionStats.totalDocsExamined:document扫描个数, 和早期版本中的nscannedObjects 是同样的 executionStats.executionStages:整个winningPlan执行树的详细信息,一个executionStages包含一个或者多个inputStages executionStats.executionStages.stage:这里是FETCH去扫描对于documents,后面会专门用来解释大部分查询使用到的各类stage的意思 executionStats.executionStages.nReturned:因为是FETCH,因此这里该值与executionStats.nReturned一致 executionStats.executionStages.docsExamined:与executionStats.totalDocsExamined一致executionStats.inputStage中的与上述理解方式相同 explain.executionStats.executionStages.works:被查询执行阶段所操做的“工做单元(work units)”数。 explain.executionStats.executionStages.advanced:优先返回给父stage的中间结果集中文档个数 explain.executionStats.executionStages.isEOF:查询执行是否已经到了数据流的末尾 这些值的初始值都是0。Works的 值当isEOF为1时要比nReturned大1, isEOF为0是相同。
explain 结果将查询计划以阶段树的形式呈现。
每一个阶段将其结果(文档或索引键)传递给父节点。
中间节点操纵由子节点产生的文档或索引键。
根节点是MongoDB从中派生结果集的最后阶段。
COLLSCAN :全表扫描 IXSCAN:索引扫描 FETCH::根据索引去检索指定document SHARD_MERGE:各个分片返回数据进行merge SORT:代表在内存中进行了排序(与前期版本的scanAndOrder:true一致) SORT_MERGE:代表在内存中进行了排序后再合并 LIMIT:使用limit限制返回数 SKIP:使用skip进行跳过 IDHACK:针对_id进行查询 SHARDING_FILTER:经过mongos对分片数据进行查询 COUNT:利用db.coll.count()之类进行count运算 COUNTSCAN:count不使用用Index进行count时的stage返回 COUNT_SCAN:count使用了Index进行count时的stage返回 SUBPLA:未使用到索引的$or查询的stage返回 TEXT:使用全文索引进行查询时候的stage返回
db.currentOp() { "desc" : "conn632530", "threadId" : "140298196924160", "connectionId" : 632530, "client" : "11.192.159.236:57052", "active" : true, "opid" : 1008837885, "secs_running" : 0, "microsecs_running" : NumberLong(70), "op" : "update", "ns" : "mygame.players", "query" : { "uid" : NumberLong(31577677) }, "numYields" : 0, "locks" : { "Global" : "w", "Database" : "w", "Collection" : "w" }, .... },
字段 | 返回值说明 |
---|---|
client | 该请求是由哪一个客户端发起的。 |
opid | 操做的惟一标识符。说明 若是有须要,能够经过db.killOp(opid)直接终止该操做。 |
secs_running | 表示该操做已经执行的时间,单位为秒。若是该字段返回的值特别大,须要查看请求是否合理。 |
microsecs_running | 表示该操做已经执行的时间,单位为微秒。若是该字段返回的值特别大,须要查看请求是否合理。 |
ns | 该操做目标集合。 |
op | 表示操做的类型。一般是查询、插入、更新、删除中的一种。 |
locks | 跟锁相关的信息,详情请参见并发介绍,本文不作详细介绍。 |
若是发现有异常的请求,您能够找到该请求对应的opid,执行db.killOp(opid)
终止该请求。
db.system.profile.find().pretty();
分析慢请求日志,查找引发MongoDB CPU使用率升高的缘由。查看到该请求进行了全表扫描
{ "op" : "query", "ns" : "123.testCollection", "command" : { "find" : "testCollection", "filter" : { "name" : "zhangsan" }, "$db" : "123" }, "keysExamined" : 0, "docsExamined" : 11000000, "cursorExhausted" : true, "numYield" : 85977, "nreturned" : 0, "locks" : { "Global" : { "acquireCount" : { "r" : NumberLong(85978) } }, "Database" : { "acquireCount" : { "r" : NumberLong(85978) } }, "Collection" : { "acquireCount" : { "r" : NumberLong(85978) } } }, "responseLength" : 232, "protocol" : "op_command", "millis" : 19428, "planSummary" : "COLLSCAN", "execStats" : { "stage" : "COLLSCAN", "filter" : { "name" : { "$eq" : "zhangsan" } }, "nReturned" : 0, "executionTimeMillisEstimate" : 18233, "works" : 11000002, "advanced" : 0, "needTime" : 11000001, "needYield" : 0, "saveState" : 85977, "restoreState" : 85977, "isEOF" : 1, "invalidates" : 0, "direction" : "forward", ....in" } ], "user" : "root@admin" }
一般在慢请求日志中,您须要重点关注如下几点。
全表扫描(关键字: COLLSCAN、 docsExamined )
全集合(表)扫描COLLSCAN 。
当一个操做请求(如查询、更新、删除等)须要全表扫描时,将很是占用CPU资源。在查看慢请求日志时发现COLLSCAN关键字,极可能是这些查询占用了CPU资源。
说明:
若是这种请求比较频繁,建议对查询的字段创建索引的方式来优化。
经过查看docsExamined的值,能够查看到一个查询扫描了多少文档。该值越大,请求所占用的CPU开销越大。
不合理的索引(关键字: IXSCAN、keysExamined )
说明:
索引不是越多越好,索引过多会影响写入、更新的性能。
若是您的应用偏向于写操做,索引可能会影响性能。
经过查看keysExamined字段,能够查看到 一个使用了索引的查询,扫描了多少条索引。该值越大,CPU开销越大。
若是索引创建的不太合理,或者是匹配的结果不少。这样即便使用索引,请求开销也不会优化不少,执行的速度也会很慢。
大量数据排序(关键字: SORT、hasSortStage )
当查询请求里包含排序的时候, system.profile 集合里的hasSortStage字段会为 true 。
若是排序没法通 过索引知足,MongoDB会在查询结果中进行排序。
而排序这个动做将很是消耗CPU资源,这种状况须要对常常排序的字段创建索引的方式进行优化。
说明 当您在system.profile集合里发现SORT关键字时,能够考虑经过索引来优化排序。
其余还有诸如创建索引、aggregation(遍历、查询、更新、排序等动做的组合) 等操做也可能很是耗CPU资源,但本质上也是上述几种场景。
http://c.biancheng.net/view/6558.html
https://docs.mongodb.org/v3.0/reference/explain-results/
https://zhuanlan.zhihu.com/p/77971681
https://www.jianshu.com/p/2b09821a365d
https://www.imooc.com/article/285899
https://blog.csdn.net/weixin_33446857/article/details/83085018
https://www.runoob.com/mongodb/mongodb-advanced-indexing.html