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
# #