MongoDB-SQL优化

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
日志服务 SLS,月写入数据量 50GB 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本文主要讲述了MongoDB的SQL优化相关的几个知识点:1)查询优化器原理;2)执行计划解析;3)性能排查手段;4)读写优化

一、MongoDB查询优化器

1、MongoDB查询优化器

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

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

2、查询优化器原理

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

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

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

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

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

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

image

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()

查看help帮助文档:
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

db.runCommand(
  {
   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}) //查询最近慢日志记录

2)查看问题SQL执行计划

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意外宕机期间日志没有落盘的可能。

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
2月前
|
存储 监控 NoSQL
MongoDB优化的几点原则
这篇文章讨论了MongoDB优化的一些原则,包括查询优化、热数据大小、文件系统选择、硬盘选择、查询方式优化、sharding key设计和性能监控。
85 1
|
1月前
|
存储 NoSQL MongoDB
掌握MongoDB索引优化策略:提升查询效率的关键
在数据库性能调优中,索引是提升查询效率的利器。本文将带你深入了解MongoDB索引的内部工作原理,探讨索引对查询性能的影响,并通过实际案例指导如何针对不同的查询模式建立有效的索引。不仅将涵盖单一字段索引,还会探讨复合索引的使用,以及如何通过分析查询模式和执行计划来优化索引,最终实现查询性能的最大化。
|
1月前
|
SQL 存储 关系型数据库
如何巧用索引优化SQL语句性能?
本文从索引角度探讨了如何优化MySQL中的SQL语句性能。首先介绍了如何通过查看执行时间和执行计划定位慢SQL,并详细解析了EXPLAIN命令的各个字段含义。接着讲解了索引优化的关键点,包括聚簇索引、索引覆盖、联合索引及最左前缀原则等。最后,通过具体示例展示了索引如何提升查询速度,并提供了三层B+树的存储容量计算方法。通过这些技巧,可以帮助开发者有效提升数据库查询效率。
117 2
|
10天前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
19天前
|
SQL 存储 缓存
如何优化SQL查询性能?
【10月更文挑战第28天】如何优化SQL查询性能?
69 10
|
18天前
|
SQL 存储 缓存
SQL Server 数据太多如何优化
11种优化方案供你参考,优化 SQL Server 数据库性能得从多个方面着手,包括硬件配置、数据库结构、查询优化、索引管理、分区分表、并行处理等。通过合理的索引、查询优化、数据分区等技术,可以在数据量增大时保持较好的性能。同时,定期进行数据库维护和清理,保证数据库高效运行。
|
1月前
|
SQL 资源调度 分布式计算
如何让SQL跑快一点?(优化指南)
这篇文章主要探讨了如何在阿里云MaxCompute(原ODPS)平台上对SQL任务进行优化,特别是针对大数据处理和分析场景下的性能优化。
|
1月前
|
SQL 监控 数据库
慢SQL对数据库写入性能的影响及优化技巧
在数据库管理系统中,慢SQL(即执行缓慢的SQL语句)不仅会影响查询性能,还可能对数据库的写入性能产生显著的不利影响
|
1月前
|
SQL 关系型数据库 PostgreSQL
遇到SQL 子查询性能很差?其实可以这样优化
遇到SQL 子查询性能很差?其实可以这样优化
88 2
|
1月前
|
存储 监控 NoSQL
TDengine 3.3.3.0 版本上线:优化监控、增强 MongoDB 支持
今天我们非常高兴地宣布,TDengine 3.3.3.0 版本正式发布。本次更新引入了多项重要功能和性能优化,旨在为用户提供更高效、更灵活的数据解决方案。
47 0
下一篇
无影云桌面