MongoDB08-优化MongoDB
- 如果使用了错误的数据结构,或者并未在集合中创建正确的索引,MongoDB的速度可能急剧下降。
- 如果数据库服务器的内存太小或者驱动(CUP或磁盘I/O)速度太低,就可能会对数据库性能产生巨大影响。
- 对于磁盘,MongoDB公司推荐使用SSD组成的RAID10(即有了性能也有了冗余)。
1、MongoDB的存储引擎
- 到目前为止,MongoDB近期历史上最大的改变是增加了新存储引擎的API。随着这个API还发布了几个存储引擎,其中,WiredTiger是最重要的。
- 在MongoDB3.0中,可以通过--storageEngine命令行参数或配置文件的storage.engine指定要使用的存储引擎。
- MMAPv1存储引擎是MongoDB 3.0之前的存储引擎。
- WiredTiger存储引擎在MongoDB 3.0引入,并在MongoDB 3.2成为默认的存储引擎。
- 在启动MongoDB时就要选择好要使用的存储引擎,否则后面更换存储引擎会比较麻烦。大多数情况下,最好使用默认的存储引擎。
- 更换存储引擎的步骤:
- 转储所有的数据,并删除所有数据库。
- 使用选择好的存储引擎启动MongoDB。
- 导入转储的所有数据。
- WiredTiger与MMAPv1存储引擎相比:
- WiredTiger使用MVCC(多版本并发控制)模型,允许MongoDB支持文档级别的锁定,而MMAPv1只能做到集合级别的锁定。
- WiredTiger可以管理自己使用的内存,而MMAPvl将内存管理委托给操作系统的内核。
- WiredTiger可以使用压缩算法对数据进行自动压缩。
1.1、MMAPv1使用内存的方式
- MMAPv1存储引警使用内存映射文件I/O来访问数据存储,这会同时受操作系统(OS)类型和内存大小的限制。
- 内存映射文件的需要注意的第一点:在64位操作系统中,Linux中的最大文件可以达到128TB左右(Linux虚拟内存地址限制),Windows对内存映射文件的限制使得最大文件最多可以达到8TB(启用日志之后则只有4TB可用)。在32位操作系统中,最大文件被限制为2GB,所以不推荐使用32位操作系统运行,除非只用于小型开发环境。
- 内存映射文件的需要注意的第二点:内存映射文件将使用操作系统的虚拟内存,系统将按需把数据文件的某个部分映射到RAM中。
- 这会产生一种有点令人吃惊的印象:MongoDB将会用尽系统的所有RAM。其实并不是这样,因为MongoDB将与其他应用一起共享虚拟地址空间。并且OS也会按需将内存释放给其他进程。
- 使用空闲内存的总数作为过度消耗内存的标志并不是个好的实践,因为好的OS只保留很少的(或者压根儿没有)“空闲”内存,所有昂贵的内存都将用于缓存硬盘I/O。空闲内存是对内存的浪费。
- 通过提供合适大小的内存,MongoDB可以在内存中保持更多它所需的数据,这将减少对磁盘的访问。
- 通常,为MongoDB分配的内存越多,它的运行速度就越快。不过,如果数据库大小只有2GB,那么添加超过3GB的内存并不会加快它的运行速度,因为整个数据库都已经在内存中了。
- MMAPv1的工作集大小:MongoDB实例中存储的数据量(“在正常使用过程中”将访问到的数据)。
- 工作集大小的计算是一种主观方式,并且难以得到准确值。因为对于大多数MongoDB实例,通常只会访问到一部分数据。了解正常工作中使用的是哪部分数据,可以帮助正确地计算出硬件的大小,从而提高MongoDB的性能。
1.2、WiredTiger使用内存的方式
- WiredTiger使用的内存模型会把尽可能多的“相关”数据有效地保存在内存中。
- 在MongoDB术语中,内存中存储文档的这个空间称为缓存。默认情况下,MongoDB使用大约一半的物理内存,用于缓存文档。可以通过--wiredTigerCacheSizeGB命令行参数或配置文件的storage.wiredTiger.engineConfig.cacheSizeGB指定缓存的大小。
- 默认的缓存大小适合大多数系统,修改这个值通常是有害的。但是,如果专用服务器有足够的内存,就可以(彻底)测试缓存更大的运行情况。
- 注意,缓存仅是用于存储文档的内存量,用于保持连接、运行数据库内部操作、执行用户操作的所有内存在其他地方计算,所以切勿把100%的系统内存都分配给MongoDB,否则操作系统将停止数据库过程!
- WiredTiger可以压缩磁盘上的数据,压缩算法有:
- none:禁用压缩
- snappy:提供了良好的压缩率,CPU使用的开销非常低。
- zlib:与snappy相比,以更高CPU使用率提供更高的压缩率。
- zstd(MongoDB 4.2):与zlib相比,以更低CPU使用率提供更高的压缩率。
- 可以压缩的MongoDB数据:
- 集合中的数据。
- 索引数据,即索引中的数据。
- 日志数据,用于确保数据有冗余,可以恢复,它们会写入长效数据存储器。
- 压缩算法在启动MongoDB实例时,已经配置好了。修改压缩算法后只影响新创建的数据对象,已经存在的数据对象将继续使用创建它们的压缩算法。例如,如果用默认的snappy压缩算法创建一个集合,然后决定给集合使用zlib压缩算法,现有的集合不会切换到snappy,但任何新的集合都使用zlib选项。
- 压缩日志(Journal)数据,使用storage.wiredTiger.engineConfig.journalCompressor配置,可用值有:none、snappy(默认)、zlib、zstd。
- 压缩索引(Index)数据:使用storage.wiredTiger.indexConfig.prefixCompression配置启用或禁用压缩索引数据。若为true(默认),则启用索引前缀压缩。
- 压缩集合(Collection)数据:使用storage.wiredTiger.collectionConfig.blockCompressor配置,可用值有:none、snappy(默认)、zlib、zstd。
2、评估查询性能
- MongoDB有两个用于分析查询性能的工具:explain()和MongoDB分析器。
- MongoDB分析器可以提供慢查询日志,将超过一定执行时间(默认100ms)的查询写到日志文件中。
- explain()可以判断一个查询的执行性能。
2.1、MongoDB分析器
- MongoDB分析器将记录统计信息,以及所有符合触发条件的查询。可以通过--profile和--slowms命令行参数。也可以添加到mongodb.conf文件中:
- operationProfiling.mode:指定哪些操作是profiled,值有:off(默认)、slowOp或all。
- operationProfiling.slowOpThresholdMs:慢查询的时间阈值,以毫秒为单位,默认值是100。
- MongoDB启用分析器在一个特殊的固定大小集合system.profile(默认大小是1024KB)中,为每个查询插入一个含有性能和执行细节的文档。
- 警告:启用MongoDB分析器会影响服务器的性能,所以不应一直在生产环境中运行它,除非正在分析一些已经发现的问题。
1、使用MongoDB分析器
- db.setProfilingLevel:启用、禁用或配置MongoDB分析器。
- MongoDB分析器会捕获并记录写操作、游标和数据库命令的性能数据。
- 如果MongoDB分析器被禁用,该函数会将慢查询记录到诊断日志中。
- 如果MongoDB分析器级别为1或2(特别是启用了数据库分析程序),则slowms、sampleRate会影响分析程序和诊断日志的行为。
- 如果MongoDB分析器级别为0(特别是禁用了数据库分析器),则slowms和sampleRate只影响诊断日志。
- setProfilingLevel函数的基本语法格式如下:
db.setProfilingLevel(<level>, <options>)
- validate函数的参数:
- <level>:
- 0:分析器处于关闭状态,不收集任何数据。这是默认的分析器级别。
- 1:分析器为耗时超过slowms值或匹配筛选器的操作收集数据。
- 当设置过滤器时:
- slowms和sampleRate选项不用于分析。
- 分析器只捕获匹配过滤器的操作。
- 当设置过滤器时:
- 2:分析器为所有操作收集数据。
- <options>:
- slowms
- sampleRate
- filter
- <level>:
- MongoDB分析器使用方法:
- 注意,下面的操作仅对当前数据库有效。
//禁用MongoDB分析器 db.setProfilingLevel(0) //启用MongoDB分析器,记录超过100毫秒(默认值)的查询 db.setProfilingLevel(1) //启用MongoDB分析器,记录超过10毫秒的查询 db.setProfilingLevel(1,10) //启用MongoDB分析器,记录所有操作 db.setProfilingLevel(2)
实例:
//设置分析器记录所有的操作
> db.setProfilingLevel(2)
{ "was" : 0, "slowms" : 100, "sampleRate" : 1, "ok" : 1 }
> db.people.find()
{ "_id" : ObjectId("63280de5b3be3694f6f15380"), "name" : "zhangsan", "age" : 11, "addr" : "shanghai" }
...
//查看分析器记录的内容
> db.system.profile.find()
{
"op" : "query", #显示操作的类型;可以是查询、插入、更新、命令或删除操作
"ns" : "db1.people", #查询所在的完整名称空间
"command" : {
"find" : "people",
"filter" : {
},
"lsid" : {
"id" : UUID("08345356-256e-4224-92b2-d395aa065e63")
},
"$db" : "db1"
},
"keysExamined" : 0,
"docsExamined" : 4,
"cursorExhausted" : true,
"numYield" : 0, #该查询为其他查询让出锁的次数
"nreturned" : 4, #返回文档的数目
"locks" : {
"FeatureCompatibilityVersion" : {
"acquireCount" : {
"r" : NumberLong(1)
}
},
"Global" : {
"acquireCount" : {
"r" : NumberLong(1)
}
},
"Mutex" : {
"acquireCount" : {
"r" : NumberLong(1)
}
}
},
"flowControl" : {
},
"responseLength" : 470, #响应的字节长度
"protocol" : "op_msg",
"millis" : 0, #执行查询所花费的毫秒数
"planSummary" : "COLLSCAN",
"execStats" : {
"stage" : "COLLSCAN",
"nReturned" : 4,
"executionTimeMillisEstimate" : 0,
"works" : 6,
"advanced" : 4,
"needTime" : 1,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"direction" : "forward",
"docsExamined" : 4
},
"ts" : ISODate("2022-09-19T07:14:33.305Z"), #以UTC格式显示出查询执行时的时间戮
"client" : "127.0.0.1", #运行该查询的客户端的连接信息
"appName" : "MongoDB Shell",
"allUsers" : [ ],
"user" : "" #运行该操作的用户
}
2、设置分析器集合的大小
//(1)禁用当前数据库上的分析器,确保不会对system.profile有写入操作
> db.setProfilingLevel(0)
{ "was" : 2, "slowms" : 100, "sampleRate" : 1, "ok" : 1 }
//(2)删除system.profile集合
> db.system.profile.drop()
true
//(3)创建一个固定集合,大小为50MB
> db.createCollection("system.profile", {capped:true, size:50*1024*1024})
{ "ok" : 1 }
//(4)启用MongoDB分析器
> db.setProfilingLevel(2)
{ "was" : 0, "slowms" : 100, "sampleRate" : 1, "ok" : 1 }
2.2、使用explain()分析的查询
- 如果怀疑某个查询的性能不佳,可以使用explain()检查MongoDB是如何执行该查询的。
- 在查询中添加explain()后,MongoDB将返回一个文档来描述它是如何处理该查询的。
- explain的基本使用方法如下:
//使用explain()函数
db.collection.find().explain()
//使用$explain操作符
db.collection.find()._addSpecial( "$explain", 1 )
db.collection.find( { $query: {}, $explain: 1 } )
示例:
> db.people.find().explain()
{
"explainVersion" : "1", #输出格式版本(例如,"1")
"queryPlanner" : { #执行查询的细节,包括计划的细节
"namespace" : "db1.people",
"indexFilterSet" : false, #表明是否使用索引过滤器来实现这个查询
"parsedQuery" : { #正在运行的查询。这是查询修改后的形式,显示了如何在内部评估它
},
"queryHash" : "8B3D4AB8",
"planCacheKey" : "D542626C",
"maxIndexedOrSolutionsReached" : false,
"maxIndexedAndSolutionsReached" : false,
"maxScansToExplodeReached" : false,
"winningPlan" : { #被选中来执行查询的计划
"stage" : "COLLSCAN",
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"command" : { #详细说明了正在执行的命令;
"find" : "people",
"filter" : {
},
"$db" : "db1"
},
"serverInfo" : { #执行该查询的服务器
"host" : "11",
"port" : 27017,
"version" : "5.0.11",
"gitVersion" : "d08c3c41c105cde798ca934e3ac3426ac11b57c3"
},
"serverParameters" : { #详细说明了内部参数
"internalQueryFacetBufferSizeBytes" : 104857600,
"internalQueryFacetMaxOutputDocSizeBytes" : 104857600,
"internalLookupStageIntermediateDocumentMaxSizeBytes" : 104857600,
"internalDocumentSourceGroupMaxMemoryBytes" : 104857600,
"internalQueryMaxBlockingSortMemoryUsageBytes" : 104857600,
"internalQueryProhibitBlockingMergeOnMongoS" : 0,
"internalQueryMaxAddToSetBytes" : 104857600,
"internalDocumentSourceSetWindowFieldsMaxMemoryBytes" : 104857600
},
"ok" : 1
}
1
# #

浙公网安备 33010602011771号