天天看点

MongoDB-SQL优化

一、MongoDB查询优化器

1、MongoDB查询优化器

1)MongoDB查询优化器会选择最优的一条执行计划来执行SQL。

2)查询优化器会缓存那些有多条可用索引的SQL的执行计划条目

2、查询优化器原理

1)对于每个SQL,查询优化器会先在在执行计划缓存中查找执行计划

2)如果没有匹配到相应的执行计划,查询优化器会生成备选执行计划,并评估他们各自的消耗,选择中最佳执行计划,并将23)这些执行计划放入缓存中

3)MongoDB根据最优执行计划返回结果

4)如果匹配到可用的执行计划,MongoDB会通过replanning的机制再次评估当前执行计划的性能

5)如果评估成功,使用该执行计划返回结果

6)如果评估失败,查询优化器重复2)操作,最终选择最优执行计划返回结果

MongoDB-SQL优化

3、执行计划缓存刷新机制

1)删除集合或者索引会重新刷新执行计划缓存

2)执行计划缓存在MongoDB重启后会失效

3)MongoDB2.6版本之后可以执行db.collection.getPlanCache().clear()手动刷新执行计划缓存

二、执行计划解析

1、语法

方法一:

db.collection.find().explain()

查看help帮助文档:

db.collection.explain().help()

方法二:

db.collection.explain().find()

db.collection.explain().find().help()

2、执行计划的三种模式

queryPlanner Mode:只会显示 winning plan 的 queryPlanner,自建MongoDB默认模式

executionStats Mode:只会显示 winning plan 的 queryPlanner + executionStats

allPlansExecution Mode:会显示所有执行计划的 queryPlanner + executionStats,阿里云MongoDB默认模式

不论哪种模式下,查看一个SQL的执行计划,都是通过查询优化器来判断的,对于所有写操作,查询执行计划只会限制起操作的消耗,不会执行操作进行变更。

3、Mongodb 执行计划解析

MongoDB执行计划主要分为两个部分:queryPlanner、executionStats

示例集合:

db.asir.find().limit(1).pretty()

{

   "_id" : ObjectId("5d3954a3cd19f9203957cea4"),

   "id" : 0,

   "name" : "sakw",

   "age" : 18,

   "date" : ISODate("2019-07-25T07:05:07.695Z")

}

db.asir.createIndex({age:1})

db.asir.createIndex({name:1,age:1})

示例查询:

db.asir.find({name:"aa",age:{$gte:28},date:ISODate("2019-07-25T07:05:07.695Z")}).explain("executionStats")

1、queryPlanner

queryPlanner主要有三大部分:parsedQuery、winningPlan、rejectedPlans。

我们关注比较多的是winningPlan,查看SQL当前执行走了什么索引

queryPlanner: {
    plannerVersion: 1,
    namespace: "test.asir",        //database.collection
    indexFilterSet: false,           //针对该query是否有indexfilter
    parsedQuery: {             //执行计划解析
    },
    winningPlan: {             //最终执行计划
    },
    rejectedPlans: [             //竞争执行计划
    ]
}           
1)parsedQuery - SQL解析

该部分解析了SQL的所有过滤条件

"queryPlanner" : {                                                            
    "plannerVersion" : 1,                                                       
    "namespace" : "test.asir",                                                    //database.collection   
    "indexFilterSet" : false,                                                        //该类查询是否使用indexfilter   
    "parsedQuery" : {                                                                        //查询解析                  
        "$and" : [                                                                
            {                                                                       
                "date" : {                                                            
                    "$eq" : ISODate("2019-07-25T07:05:07.695Z")                         
                }                                                                     
            },                                                                      
            {                                                                       
                "name" : {                                                            
                    "$eq" : "aa"                                                        
                }                                                                     
            },                                                                      
            {                                                                       
                "age" : {                                                             
                    "$gte" : 28                                                         
                }                                                                     
            }                                                                       
        ]                                                                         
    },                                                                                                             
2)winningPlan - SQL最终选择的执行计划

winningPlan可以分三部分来看:stage、filter、inputStage

"winningPlan" : {                                                                             //最终选择执行计划                               
        "stage" : "FETCH",                                     //通过索引检索得到记录再次进行文档检索                     
        "filter" : {                                                                                    
            "date" : {                                           //文档检索条件                                 
                "$eq" : ISODate("2019-07-25T07:05:07.695Z")                                                 
            }                                                                                             
        },                                                                                              
        "inputStage" : {                                                                                //子stage                                
            "stage" : "IXSCAN",                                   //索引检索                                  
            "keyPattern" : {                                      //索引检索条件                                
                "name" : 1,                                                                                 
                "age" : 1                                                                                   
            },                                                                                            
            "indexName" : "name_1_age_1",                         //索引名字                                  
            "isMultiKey" : false,                                 //是否为多键索引                               
            "multiKeyPaths" : {                                                                           
                "name" : [ ],                                                                               
                "age" : [ ]                                                                                 
            },                                                                                            
            "isUnique" : false,                                    //是否为唯一索引                              
            "isSparse" : false,                                    //是否为稀疏索引                              
            "isPartial" : false,                                   //是否为部分索引                              
            "indexVersion" : 2,                                    //索引版本                                 
            "direction" : "forward",                                                                      
            "indexBounds" : {                                      //索引范围                                 
                "name" : [                                                                                  
                    "[\"aa\", \"aa\"]"                                                                        
                ],                                                                                          
                "age" : [                                                                                   
                    "[28.0, inf.0]"                                                                           
                ]                                                                                           
            }                                                                                             
        }                                                                                               
    },                                                                                                                                                                                                           
3)rejectedPlans - 被淘汰的执行计划
"rejectedPlans" : []      //竞选失败的执行计划               

2、executionStats

最好的情况是:nReturned = totalKeysExamined = totalDocsExamined

"executionStats" : {                                                                                                   
    "executionSuccess" : true,                                                                     //是否执行成功                                                
    "nReturned" : 1,                                             //返回记录数                                                 
    "executionTimeMillis" : 0,                                   //SQL总执行时间消耗,ms                                         
    "totalKeysExamined" : 11,                                    //索引扫描数                                                 
    "totalDocsExamined" : 11,                                    //文档扫描数                                                 
    "executionStages" : {                                                                                                
        "stage" : "FETCH",                                         //通过索引检索得到记录再次进行文档扫描的过程                                 
        "filter" : {                                                                                                       
            "date" : {                                                                                                       
                "$eq" : ISODate("2019-07-25T07:05:07.695Z")                                                                    
            }                                                                                                                
        },                                                                                                                 
        "nReturned" : 1,                                                                                                   
        "executionTimeMillisEstimate" : 0,                          //文档扫描时间消耗,ms                                          
        "works" : 13,                                               //期间所操作的工作单元个数                                         
        "advanced" : 1,                                             //优先返回给父stage的中间结果集中文档个数                               
        "needTime" : 10,                                                                                                   
        "needYield" : 0,                                            //请求查询阶段暂停处理并产生锁定的次数                                   
        "saveState" : 0,                                            //查询阶段暂停处理并保存其当前执行状态的次数                                
        "restoreState" : 0,                                         //查询阶段恢复已保存的执行状态的次数                                    
        "isEOF" : 1,                                                                                                       
        "invalidates" : 0,                                                                                                 
        "docsExamined" : 11,                                                                                               
        "alreadyHasObj" : 0,                                                                                               
        "inputStage" : {                                                                                         //索引检索阶段                                              
            "stage" : "IXSCAN",                                                                                              
            "nReturned" : 11,                                                                                                
            "executionTimeMillisEstimate" : 0,                                                                               
            "works" : 12,                                                                                                    
            "advanced" : 11,                                                                                                 
            "needTime" : 0,                                                                                                  
            "needYield" : 0,                                                                                                 
            "saveState" : 0,                                                                                                 
            "restoreState" : 0,                                                                                              
            "isEOF" : 1,                                                                                                     
            "invalidates" : 0,                                                                                               
            "keyPattern" : {                                                                                                 
                "name" : 1,                                                                                                    
                "age" : 1                                                                                                      
            },                                                                                                               
            "indexName" : "name_1_age_1",                                                                 //使用索引名称                                            
            "isMultiKey" : false,                                                                                            
            "multiKeyPaths" : {                                                                                              
                "name" : [ ],                                                                                                  
                "age" : [ ]                                                                                                    
            },                                                                                                               
            "isUnique" : false,                                                                                              
            "isSparse" : false,                                                                                              
            "isPartial" : false,                                                                                             
            "indexVersion" : 2,                                                                                              
            "direction" : "forward",                                                                                         
            "indexBounds" : {                                                                                                
                "name" : [                                                                                                     
                    "[\"aa\", \"aa\"]"                                                                                           
                ],                                                                                                             
                "age" : [                                                                                                      
                    "[28.0, inf.0]"                                                                                              
                ]                                                                                                              
            },                                                                                                               
            "keysExamined" : 11,                                                                                             
            "seeks" : 1,                                                                                                     
            "dupsTested" : 0,                                                                                                
            "dupsDropped" : 0,                                                                                               
            "seenInvalidated" : 0                                                                                            
        }                                                                                                                  
    }                                                                                                                    
},                                                                                                                                                              

3、serverInfo 服务器信息

阿里云MonogoDB实例上其实将这个信息隐藏掉了。

"serverInfo" : {                                           
    "host" : "dbslave2",                                         //主机                       
    "port" : 28002,                                              //端口
    "version" : "4.0.10-5",                                      //版本
    "gitVersion" : "7dab0a3a7b7b40cf71724b5a11eff871f8c3885c"    //MongoDB Server git版本号
},                                                         
           

4、indexFilterSet

indexFilter仅仅决定对于该查询MongoDB可选择的索引是由什么决定的。若indexFilterSet为true,说明该查询只能选择indexFilter设置的一些可选索引,最终选择使用哪个索引由优化器决定;若indexFilterSet=false,说明该查询可以选择该集合所有的索引,最终选择使用哪个索引由优化器确定。

1)如何设置indexFilter

db.runCommand(

  {

   planCacheSetFilter: <collection>,   //需要创建indexFilter集合

   query: <query>,              //指定哪类查询使用indexFilter

   sort: <sort>,              //排序条件

   projection: <projection>,        //查询字段

   indexes: [ <index1>, <index2>, ...]      //indexFilter可使用索引

  }

)

2)如何删除indexFilter

   planCacheClearFilters: <collection>,   //指定集合

   query: <query pattern>,     //指定查询类别

   sort: <sort specification>,     //排序条件

   projection: <projection specification>    //查询字段

3)如何查看一个集合所有的indexFilter

db.runCommand( { planCacheListFilters: <collection> } )

4)示例:

集合数据如下:

db.scores.find()

{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }

{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }

{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }

{ "_id" : ObjectId("5d303213cd8afaa592e23990"), "userid" : "AAAAAAA", "score" : 43 }

{ "_id" : ObjectId("5d303213cd8afaa592e23991"), "userid" : "BBBBBBB", "score" : 34 }

{ "_id" : ObjectId("5d303213cd8afaa592e23992"), "userid" : "CCCCCCC" }

{ "_id" : ObjectId("5d303213cd8afaa592e23993"), "userid" : "DDDDDDD" }

db.scores.createIndex({userid:1,score:1})

创建indexFilter:

> db.runCommand(
...    {
...       planCacheSetFilter: "scores",
...       query: { userid: "abby" },
...       indexes: [
...          { "userid" : 1, "score" : 1},              
...       ]
...    }
... )
{ "ok" : 1 }           

查看执行计划:

> db.scores.find({userid: "abbyc"}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.scores",
        "indexFilterSet" : true,                        //表示使用了indexFilter指定的索引
        "parsedQuery" : {
            "userid" : {
                "$eq" : "abbyc"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "userid" : 1,
                    "score" : 1
                },
                "indexName" : "userid_1_score_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "userid" : [ ],
                    "score" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "userid" : [
                        "[\"abbyc\", \"abbyc\"]"
                    ],
                    "score" : [
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "dbslave2",
        "port" : 28002,
        "version" : "4.0.10-5",
        "gitVersion" : "7dab0a3a7b7b40cf71724b5a11eff871f8c3885c"
    },
    "ok" : 1
}           

5)注意点

   当使用index filter的时候,使用hint强制走index filter之外的索引会失效,

4、stage类型

stage的类型:

COLLSCAN:全表扫描

IXSCAN:索引扫描

FETCH:根据索引去检索指定document

SHARD_MERGE:将各个分片返回数据进行merge

SORT:表明在内存中进行了排序

LIMIT:使用limit限制返回数

SKIP:使用skip进行跳过

IDHACK:针对_id进行查询

SHARDING_FILTER:通过mongos对分片数据进行查询

COUNT:利用db.coll.explain().count()之类进行count运算

COUNTSCAN:count不使用Index进行count时的stage返回

COUNT_SCAN:count使用了Index进行count时的stage返回

SUBPLA:未使用到索引的$or查询的stage返回

TEXT:使用全文索引进行查询时候的stage返回

PROJECTION:限定返回字段时候stage的返回

对于普通查询,我希望看到stage的组合(查询的时候尽可能用上索引):

Fetch+IDHACK

Fetch+ixscan

Limit+(Fetch+ixscan)

PROJECTION+ixscan

SHARDING_FITER+ixscan

COUNT_SCAN

如下的stage效率比较低下:

COLLSCAN(全表扫描)

SORT(使用sort但是无index)

SUBPLA(未用到index的$or)

COUNTSCAN(不使用index进行count)

三、如何排查MongoDB性能问题

1、对于当前正在发生的情况

1)查看当前会话情况,抓取正在慢的SQL

db.currentOp()
或者
db.currentOp(
   {
     "active" : true,
     "secs_running" : { "$gt" : 3 },
     "ns" : /^db1\./
   }
)           

重点关注:

client          #请求是由哪个客户端发起
opid            #操作的opid,可以通过 db.killOp(opid) 直接杀掉会话
secs_running/microsecs_running
                #这个值重点关注,代表请求运行的时间,如果这个值特别大,就得注意了,看看请求是否合理
query/ns:       #这个能看出是对哪个集合正在执行什么操作
lock*:         #还有一些跟锁相关的参数           

2)查看问题SQL执行计划

db.collection.find().explain()           

2、对于历史问题

1)查看慢日志以及运行日志

如何调整慢日志参数:

查看当前慢日志设置参数
> db.getProfilingStatus()                   //查看当前慢日志参数状态
{ "was" : 1, "slowms" : 100 }

动态修改慢日志参数
> db.setProfilingLevel(1,10)                //动态修改参数
{ "was" : 1, "slowms" : 100, "ok" : 1 }
> db.getProfilingStatus()
{ "was" : 1, "slowms" : 10 }           

慢日志参数解释:

was:  慢日志模式  
    0:不开启慢日志
    1:开启慢日志,只记录超过一定阈值的慢SQL
    2:开启慢日志,记录所有操作

slowms:慢SQL阈值,单位为ms
           

查看慢日志信息:

> db.system.profile.find().sort({$natrual: -1}) //查询最近慢日志记录           
db.collection.find().explain()           

四、读写操作的一些优化

1、查询优化

1)建立合适索引

   1.在选择性较好的字段建立索引

   2.单列索引不需要考虑升降序,但是复合索引可以根据业务需求创建对应升降序的复合索引

   3.覆盖索引查询,查询和过滤的所有字段都在复合索引中

db.inventory.createIndex( { type: 1, item: 1 } )

    db.inventory.find(
    { type: "food", item:/^c/ },
    { item: 1, _id: 0 } )                         //需要强制_id字段不显示以保证走覆盖索引查询

           

2)使用limit限定返回结果,减轻网络开销

3)需要哪些字段查询哪些字段

4)使用hint强制走指定索引

2、写操作优化

1)集合上的索引会增加该集合写入/更新操作的资源消耗,适度创建索引

MMAPv1存储引擎中,当一个update操作所需要的空间超过了原本分配的空间时,MMAPv1存储引会将该文档移动到磁盘上一个新的位置,并全部将该集合的索引进行更新指向新的文档位置,整个过程是非常消耗资源的。

从MongoDB 3.0开始,MonogoDB使用 Power of 2 Sized Allocations,保证MongoDB尽量对空间的空间重用,尽量减少重新分配空间位置的发生。

2)硬件优化,固态SSD的性能要优于HDDs

3)合理设置journal相关参数

   1.journal日志实现日志预写功能,开启journal保证了数据的持久化,但也存在一定的性能消耗

   2.尽量将数据文件与journal日志文件放在不同的磁盘喜爱,避免I/O资源争用,提高写操作能力

   3.j:true参数会增加写操作的负载,根据业务实际情况合理使用write concern参数

   4.设置合理的commitIntervalMs参数

      减小该参数会减少日志提交的时间间隔、增加写操作的数量,但是会限制MongoDB写能力。

      增大该参数会增加日志提交的时间间隔、减少写操作的数量,但是增加了MongoDB意外宕机期间日志没有落盘的可能。