# 性能优化
# 服务器状态
- 在 shell 中,可用 mongostat、mongostop 命令查看 mongod 的状态。
- 在 mongo 客户端中,可以用以下命令查看 mongod 的状态:
db.serverStatus() // 查看服务器的状态信息 db.serverStatus().connections // 查看客户端连接数 db.currentOp() // 查看数据库正在执行的所有操作 db.currentOp({op: "query", active: true}) // 只返回符合查询条件的操作 db.killOp(<opid>) // 终止一个操作
# 内存
mongod 占用内存的因素包括:
- mongod 的程序文件
- mongod 刚启动时会加载程序文件,此时只占用 100MB 内存,其中 WiredTiger Cache 几乎为 0 。等之后读写数据,才会占用更多内存。
- collection、index 的元数据
- 客户端连接
- mongod 不限制客户端连接数,为每个连接创建一个最多占用 1MB 栈内存的线程。还会因为 Socket 占用内核内存。
- WiredTiger Cache
- mongod 需要读取某条文档时,先找到磁盘中的数据文件 collection-xx.wt ,读取包含目标数据的那个文件块,经过 Page Cache(外部缓存),然后解压并载入 WiredTiger Cache(内部缓存),最后根据 _id 在 B+ tree 结构中找到那条数据。
- 查询一个集合时,可能读取其 index 的部分或全部内容,载入内部缓存。写入文档时,也会更新 index 。
- 内部缓存不包括构建索引时占用的内存。在单个集合上构建索引时最多占用内存为 maxIndexBuildMemoryUsageMegabytes(默认 500MB )。
- Page Cache
- WiredTiger 读写磁盘文件时,不使用 MMAP 技术,而是依赖操作系统的文件缓存。默认将 50% 的主机内存用作内部缓存,剩下的 50% 用作 Page Cache ,从而加速读写磁盘。
- Page Cache 不是 mongod 进程主动申请的内存,而是被操作系统自动分配的。减少 Page Cache 不会停止 mongod 进程,只是可能减慢读写磁盘的速度。
- mongod 的程序文件
WiredTiger 会跟踪内部缓存中 clean pages 和 dirty pages 的体积。
- 内部缓存达到 cacheSizeGB 的 eviction_target(默认 80% )时,后台的 evict 线程会开始驱逐内存中较少使用的 clean pages ,腾出内存空间。
- 内部缓存达到 eviction_trigger(默认 95% )时,前台线程也开始驱逐 clean pages ,导致读写操作变慢。
- 内部缓存达到 100% 时,会停止数据库的读写操作。
- dirty pages 达到 eviction_dirty_target(默认 5% )时,后台的 evict 线程会开始驱逐 dirty pages ,导致丢失数据。
- dirty pages 达到 eviction_dirty_trigger(默认 20% )时,前台线程也开始驱逐 dirty pages ,导致读写操作变慢。
- 如果占用内存长时间超过驱逐阈值,则说明 mongod 的内存不足。
例:查看服务器的内存开销
> db.serverStatus().tcmalloc.tcmalloc.formattedString ------------------------------------------------ MALLOC: 29943407472 (28556.3 MiB) Bytes in use by application // mongod 进程正在使用的内存,用于存放文档、索引等数据 MALLOC: + 6028980224 ( 5749.7 MiB) Bytes in page heap freelist // 堆内存中的空闲空间 MALLOC: + 1164940264 ( 1111.0 MiB) Bytes in central cache freelist MALLOC: + 0 ( 0.0 MiB) Bytes in transfer cache freelist // transfer cache 用于在 central cache 与 thread cache 之间传输数据 MALLOC: + 788441256 ( 751.9 MiB) Bytes in thread cache freelists MALLOC: + 220553472 ( 210.3 MiB) Bytes in malloc metadata MALLOC: ------------ MALLOC: = 38146322688 (36379.2 MiB) Actual memory used (physical + swap) // mongod 进程在操作系统中占用的内存,等于上面几项内存之和 MALLOC: + 1304309760 ( 1243.9 MiB) Bytes released to OS (aka unmapped) MALLOC: ------------ MALLOC: = 39450632448 (37623.1 MiB) Virtual address space used MALLOC: MALLOC: 2613472 Spans in use MALLOC: 4708 Thread heaps in use MALLOC: 4096 Tcmalloc page size
- mongod 默认采用 tcmalloc 内存分配器,会将空闲内存释放给操作系统,但可能不会立即释放。
查看数据体积:
db.stats().dataSize // 数据库中全部文档的体积,单位 bytes 。这是读取到内存时的体积,没有压缩 db.stats().storageSize // 全部文档占用的磁盘空间。可能比 dataSize 小,因为 WiredTiger 存储引擎默认进行压缩。也可能比 dataSize 大,因为删除的文档不会释放磁盘空间 db.stats().indexSize // 全部索引占用的磁盘空间 db.stats().totalSize // 等于 storageSize + indexSize ,包括已分配但尚未使用的空闲磁盘空间 db.stats().freeStorageSize // 文档已分配但尚未使用的空闲磁盘空间。这是 Mongo v5.0 新增的监控指标 db.stats().indexFreeStorageSize // 索引已分配但尚未使用的空闲磁盘空间 db.<collection>.stats().wiredTiger["block-manager"]["file size in bytes"] // 集合占用的磁盘空间 db.<collection>.stats().wiredTiger["block-manager"]["file bytes available for reuse"] // 集合占用的磁盘空间中,标记为 deleted 的空间 db.<collection>.stats().wiredTiger["cache"]["bytes currently in the cache"] // 集合占用的内存体积 db.<collection>.stats().wiredTiger["cache"]["bytes read into cache"] // 累计从磁盘读取到内存的数据体积 db.<collection>.stats().wiredTiger["cache"]["bytes written from cache"] // 累计从内存写入磁盘的数据体积 db.<collection>.stats().wiredTiger["cache"]["tracked dirty bytes in the cache"] // 内存中的脏页数 db.<collection>.stats().indexSizes // 查看各个索引占用的磁盘空间 db.<collection>.aggregate([{$indexStats:{}}]) // 查看各个索引的累计使用次数 db.serverStatus().wiredTiger["cache"]["pages requested from the cache"] // 累计从内存读取的数据量。如果查询的数据在内存中不存在,则需要从磁盘读取 db.serverStatus().wiredTiger["cache"]["pages read into cache"] // 累计从磁盘读入内存的数据量 db.serverStatus().metrics.query.planCacheTotalSizeEstimateBytes // planCache 占用的内存
# explain
- 在 mongo 客户端执行命令时,可加上 explain() 方法,显示统计信息。
- explain() 有多种工作模式:
queryPlanner // 显示当前操作的执行计划,不会实际执行操作 executionStats // 执行当前操作,然后显示统计信息 allPlansExecution // 先显示执行计划,然后执行当前操作、显示统计信息
- 可通过以下格式调用 explain() 方法:例:
db.<collection>.<method>.explain() // 默认采用 queryPlanner 模式 db.<collection>.explain().<method>
> db.books.find({"group": "art"}).explain('allPlansExecution') { "queryPlanner": { "plannerVersion": 1, "namespace": "test.books", // 当前位于的 db.collection "indexFilterSet": false, "parsedQuery": { // 将查询语句解析成基础指令 "group": { "$eq": "art" } }, "winningPlan": { // 准备了多个执行计划,从中选出效率最高的一个计划来执行,其它计划记录到 rejectedPlans 字段 "stage": "FETCH", // FETCH 表示根据索引去获取集合中的文档。IXSCAN 表示读取索引。COLLSCAN 表示全表扫描,不使用索引。IDHACK 表示根据 _id 直接获取文档,不使用索引 "filter": {}, // 在 inputStage 之后,筛选文档 "inputStage": { // 输入数据的阶段,负责查询文档 "stage": "IXSCAN", "keyPattern": { // 该索引包含哪些字段 "group": 1 }, "indexName": "group_1", // 使用的索引名 "indexBounds": { // 根据 parsedQuery 中哪些字段的取值上下边界,来读取索引 "group": [ "[\"art\", \"art\"]" ], } } }, "rejectedPlans": [] }, "executionStats": { "executionSuccess": true, // 操作是否执行成功 "nReturned": 829330, // 返回的文档数 "executionTimeMillis": 4825, // 操作的执行耗时 "totalKeysExamined": 829330, // 在索引中读取了多少条目。如果为 0 ,说明没有使用索引,或在索引中没有查询到匹配的文档 "totalDocsExamined": 829330, // 在集合中读取了多少文档。这里 totalDocsExamined 等于 nReturned ,说明成功用索引提高了查询效率,没有读取无关的文档 "executionStages": { ... }, "allPlansExecution": [] }, "serverInfo": { "host": "c442af87b30b", "port": 27017, "version": "4.4.17", "gitVersion": "85de0cc83f4dc64dbbac7fe028a4866228c1b5d1" }, "ok": 1 }