MongoDB入门学习教程,从入门到精通,MongoDB的了解应用程序的动态(18)

了解应用程序的动态

在 MongoDB 生产环境中,了解数据库当前正在执行的操作、资源消耗、数据大小以及实时吞吐量是性能调优和故障排查的基础。本章详细介绍四种关键方法:查看当前操作、使用系统分析器、计算集合/文档/索引大小,以及使用 mongotopmongostat 命令行工具。所有案例基于 MongoDB 5.0+,并附有详尽注释。


1. 查看当前操作

db.currentOp() 命令用于获取数据库当前正在执行的所有操作(包括增删改查、索引构建、数据迁移等)。通过分析这些操作,可以定位慢查询、长时间运行的事务或锁冲突。

1.1 基本用法与输出字段

语法
db.currentOp(<filter>)

  • 不带参数:返回所有当前操作。
  • 带参数:传入查询条件过滤结果。

案例代码

javascript 复制代码
// 连接到 mongos 或 mongod(根据环境)
mongosh --port 27017

// 查看所有当前操作
db.currentOp()
// 返回一个文档,其中 "inprog" 数组包含每个操作,关键字段:
//   - "opid"       : 操作唯一ID,可用于终止操作
//   - "active"     : 是否正在执行(true/false)
//   - "secs_running": 已运行秒数
//   - "op"         : 操作类型(query/insert/update/delete/command/getmore等)
//   - "ns"         : 目标命名空间(数据库.集合)
//   - "query"      : 查询具体内容(或过滤条件)
//   - "planSummary": 执行计划摘要(如 IXSCAN、COLLSCAN)
//   - "client"     : 客户端地址
//   - "waitingForLock": 是否在等待锁
//   - "locks"      : 持有的锁类型

// 只显示非空闲的活跃操作(过滤掉 idle 连接)
db.currentOp({ "active": true })

// 显示运行时间超过 3 秒的操作
db.currentOp({
  "active": true,
  "secs_running": { $gt: 3 }
})

// 显示特定命名空间的操作(如数据库 test 下的 orders 集合)
db.currentOp({ "ns": "test.orders" })

// 显示特定操作类型(例如 update 操作)
db.currentOp({ "op": "update" })

// 显示正在等待锁的操作(可能造成性能瓶颈)
db.currentOp({ "waitingForLock": true })

1.2 终止操作

语法
db.killOp(<opid>) -- 终止指定操作。

案例代码

javascript 复制代码
// 1. 先找到要终止的操作的 opid
db.currentOp({ "active": true, "secs_running": { $gt: 60 } })
// 假设返回的 opid 为 12345

// 2. 终止该操作
db.killOp(12345)
// 返回 { "info": "attempting to kill op", "ok": 1 }

// 3. 验证操作是否已被终止(再次查询该 opid)
db.currentOp({ "opid": 12345 })
// 如果操作已结束,返回空数组

// 批量终止所有运行超过 10 分钟的查询操作(谨慎使用)
let ops = db.currentOp({ "active": true, "secs_running": { $gt: 600 }, "op": "query" })
ops.inprog.forEach(function(op) {
  print("Killing op " + op.opid + " from " + op.client)
  db.killOp(op.opid)
})

1.3 聚合统计当前操作

通过聚合框架分析 currentOp 输出,可以获得全局负载画像。

案例代码

javascript 复制代码
// 统计当前不同操作类型的数量
let ops = db.currentOp().inprog
let stats = {}
ops.forEach(op => { stats[op.op] = (stats[op.op] || 0) + 1 })
printjson(stats)
// 输出示例:{ "query": 12, "command": 5, "update": 2 }

// 统计正在使用 COLLSCAN(全表扫描)的操作
let collscans = ops.filter(op => op.planSummary && op.planSummary.includes("COLLSCAN"))
print("Operations with COLLSCAN: " + collscans.length)
collscans.forEach(op => print(op.ns, op.query))

// 找出持有写锁时间最长的操作(按 secs_running 降序)
ops.filter(op => op.locks && op.locks.Global === "W")
   .sort((a,b) => b.secs_running - a.secs_running)
   .slice(0, 5)
   .forEach(op => print(`OPID: ${op.opid}, Running: ${op.secs_running}s, NS: ${op.ns}`))

2. 使用系统分析器

系统分析器(Database Profiler)捕获在数据库上执行的操作的详细信息,包括耗时、读写锁、扫描文档数等。常用于分析慢查询和优化索引。

2.1 开启与分析级别

语法
db.setProfilingLevel(<level>, <options>)

  • level:0(关闭)、1(记录慢查询)、2(记录所有操作)。
  • options{ slowms: 100, sampleRate: 0.5 } 等。

案例代码

javascript 复制代码
// 查看当前分析器状态
db.getProfilingStatus()
// 返回 { "was": 0, "slowms": 100, "sampleRate": 1 } 表示关闭

// 开启慢查询分析,记录超过 100 毫秒的操作(默认值)
db.setProfilingLevel(1, { slowms: 100 })
// 返回 { "ok": 1, "was": 0 }

// 设置更严格的慢查询阈值(50ms),并采样 50% 的操作
db.setProfilingLevel(1, { slowms: 50, sampleRate: 0.5 })

// 记录所有操作(调试用,生产环境慎用,会严重影响性能)
db.setProfilingLevel(2)

// 关闭分析器
db.setProfilingLevel(0)

// 修改全局默认的 slowms(影响所有数据库,需要重启或 setParameter)
db.adminCommand({ setParameter: 1, slowms: 200 })

2.2 查询分析器数据

分析器将数据写入 system.profile 集合(位于每个数据库中)。可以直接查询该集合。

案例代码

javascript 复制代码
// 确保已经开启分析器(level>=1)
db.setProfilingLevel(1)

// 执行一些测试操作
db.users.find({ age: { $gt: 30 } }).explain("executionStats")
db.users.updateOne({ name: "Alice" }, { $set: { status: "active" } })

// 查询最近记录的 5 条分析器条目
db.system.profile.find().sort({ ts: -1 }).limit(5).pretty()

// 查询所有耗时超过 200ms 的操作
db.system.profile.find({ millis: { $gt: 200 } }).pretty()

// 查询特定集合的操作(例如 test.users)
db.system.profile.find({ ns: "test.users" }).pretty()

// 查询全表扫描(COLLSCAN)的慢查询
db.system.profile.find({ "planSummary": "COLLSCAN", millis: { $gt: 0 } }).pretty()

// 查询扫描文档数超过 10000 的操作
db.system.profile.find({ nscanned: { $gt: 10000 } }).pretty()

// 聚合分析:找出最耗时的前 10 个操作
db.system.profile.aggregate([
  { $match: { op: { $ne: "command" } } },
  { $group: { _id: "$query", avgMillis: { $avg: "$millis" }, count: { $sum: 1 } } },
  { $sort: { avgMillis: -1 } },
  { $limit: 10 }
]).pretty()

2.3 分析器数据清理与维护

system.profile 集合是固定大小集合(capped collection),默认大小为 1MB。当写满时会覆盖最旧的记录。

案例代码

javascript 复制代码
// 查看当前 system.profile 的大小限制
db.system.profile.stats().cappedMaxSize
// 或
db.getProfilingStatus()  // 不直接显示大小

// 修改 system.profile 的大小(需要先关闭分析器,删除集合,再重新创建)
db.setProfilingLevel(0)
db.system.profile.drop()
db.createCollection("system.profile", { capped: true, size: 4 * 1024 * 1024 }) // 4MB
db.setProfilingLevel(1)

// 清空分析器数据(直接删除文档,不会改变集合属性)
db.system.profile.deleteMany({})

3. 计算大小

在 MongoDB 中,了解文档、集合、索引、数据库的磁盘占用大小对于容量规划和性能优化至关重要。MongoDB 提供了多个命令和函数来精确计算各种大小。

3.1 文档大小

语法
Object.bsonsize(<document>) -- 计算一个 BSON 文档的字节数。

案例代码

javascript 复制代码
// 计算单个文档的大小
let doc = db.users.findOne({ name: "Alice" })
let size = Object.bsonsize(doc)
print(`Document size: ${size} bytes (${size / 1024} KB)`)

// 计算集合中文档的平均大小
let stats = db.users.stats()
let avgObjSize = stats.avgObjSize
print(`Average document size: ${avgObjSize} bytes`)

// 找出集合中最大的几个文档
db.users.aggregate([
  { $project: { name: 1, sizeInBytes: { $bsonSize: "$$ROOT" } } },
  { $sort: { sizeInBytes: -1 } },
  { $limit: 5 }
]).forEach(doc => print(`${doc.name}: ${doc.sizeInBytes} bytes`))

3.2 集合大小

语法
db.collection.stats() -- 返回集合的详细统计信息,包括大小。
db.collection.dataSize() -- 仅返回集合数据的总字节数。
db.collection.storageSize() -- 返回集合占用的磁盘存储空间(包括预分配和删除空间)。

案例代码

javascript 复制代码
// 获取集合完整统计信息
let stats = db.orders.stats()
print(`Data size (actual data): ${stats.size} bytes`)
print(`Storage size (on disk): ${stats.storageSize} bytes`)
print(`Total index size: ${stats.totalIndexSize} bytes`)
print(`Number of documents: ${stats.count}`)
print(`Average document size: ${stats.avgObjSize} bytes`)

// 仅获取数据大小
db.orders.dataSize()
// 返回数字,单位为字节

// 获取存储大小(包含已删除但未回收的空间)
db.orders.storageSize()

// 获取集合中所有文档的总数(近似)
db.orders.estimatedDocumentCount()
// 或精确计数(可能较慢)
db.orders.countDocuments()

3.3 索引大小

语法
db.collection.stats().indexSizes -- 每个索引的大小。

案例代码

javascript 复制代码
// 查看集合所有索引的大小(以字节为单位)
let indexSizes = db.users.stats().indexSizes
printjson(indexSizes)
// 输出示例:{ "_id_": 163840, "name_1": 81920, "age_-1": 40960 }

// 获取特定索引的大小
let idxSize = db.users.stats().indexSizes["name_1"]
print(`Index 'name_1' size: ${idxSize} bytes`)

// 计算所有索引总大小
let totalIdxSize = db.users.totalIndexSize()
print(`Total indexes size: ${totalIdxSize} bytes`)

// 找出集合中占用空间最大的索引
let sizes = db.users.stats().indexSizes
let maxIdx = Object.keys(sizes).reduce((a, b) => sizes[a] > sizes[b] ? a : b)
print(`Largest index: ${maxIdx}, size: ${sizes[maxIdx]} bytes`)

3.4 数据库大小

语法
db.stats() -- 当前数据库的统计信息。
show dbs -- 列出所有数据库及其数据大小。

案例代码

javascript 复制代码
// 查看当前数据库的统计信息
let dbStats = db.stats()
print(`Database name: ${dbStats.db}`)
print(`Data size: ${dbStats.dataSize} bytes (${dbStats.dataSize / 1024 / 1024} MB)`)
print(`Index size: ${dbStats.indexSize} bytes`)
print(`Total size (data+indexes): ${dbStats.fileSize} bytes`)
print(`Collections: ${dbStats.collections}`)
print(`Objects: ${dbStats.objects}`)

// 查看所有数据库的磁盘占用(需在 admin 数据库下)
use admin
db.runCommand({ listDatabases: 1, nameOnly: false }).databases.forEach(dbInfo => {
  print(`${dbInfo.name}: ${dbInfo.sizeOnDisk} bytes (${(dbInfo.sizeOnDisk / 1024 / 1024).toFixed(2)} MB)`)
})

// 计算整个 MongoDB 实例的总数据大小
use admin
let total = 0
db.runCommand({ listDatabases: 1 }).databases.forEach(d => { total += d.sizeOnDisk })
print(`Total disk usage: ${total} bytes (${total / 1024 / 1024 / 1024} GB)`)

3.5 预分配与存储碎片处理

MongoDB 的数据文件是预分配空间的,导致 storageSize 可能大于实际 dataSize。可以通过 compact 命令回收碎片。

案例代码

javascript 复制代码
// 检查集合的碎片程度(storageSize / size 比值)
let stats = db.orders.stats()
let ratio = stats.storageSize / stats.size
print(`Fragmentation ratio: ${ratio} (1.0 = no fragmentation, >1 = fragmented)`)

// 对集合进行压缩(需要节点为 PRIMARY,会阻塞读写)
db.runCommand({ compact: "orders" })
// 返回 { "ok": 1 } 表示成功

// 对于分片集合,需要在每个分片上分别执行 compact
// 使用 db.runCommand({ compact: "orders", force: true }) 在 SECONDARY 上执行

// 重建集合所有索引以减少碎片
db.orders.reIndex()

4. 使用 mongotop 和 mongostat

MongoDB 提供了两个命令行工具,用于实时监控数据库的活动和性能指标。

4.1 mongotop -- 跟踪集合级别的读写时间

mongotop 报告每个集合的读写耗时(以毫秒为单位),按命名空间分组,默认每 1 秒刷新一次。

语法
mongotop --host <host> --port <port> --username <user> --password <pass> --authenticationDatabase admin <interval>

  • interval:刷新间隔(秒),默认为 1。

案例代码

bash 复制代码
# 最简单用法:连接到本地默认端口的 mongod/mongos
mongotop

# 输出示例:
#                     ns       total    read    write  2025-04-04T10:30:00Z
#     admin.system.roles         0ms     0ms      0ms
#   admin.system.version         0ms     0ms      0ms
#      local.system.replset      0ms     0ms      0ms
#   test.orders                245ms   120ms    125ms
#   test.users                  32ms    30ms      2ms

# 每 3 秒刷新一次
mongotop 3

# 连接到远程主机(带认证)
mongotop --host 192.168.1.100 --port 27017 --username monitor --password secret --authenticationDatabase admin 2

# 仅输出特定数据库的集合(通过 grep 过滤)
mongotop 1 | grep "test\."

# 将输出记录到文件(用于事后分析)
mongotop 5 > mongotop.log &

# 退出 mongotop:按 Ctrl+C

输出字段解释

  • ns:命名空间(数据库.集合)
  • total:在该命名空间上花费的总时间
  • read:读操作耗时
  • write:写操作耗时
    readwrite 值表示该集合负载较重,可能需要优化查询或索引。

4.2 mongostat -- 全面的服务器状态统计

mongostat 提供类似于 iostat 的快速状态视图,包括插入/查询/更新/删除速率、锁百分比、网络流量、连接数等。

语法
mongostat --host <host> --port <port> --username <user> --password <pass> --authenticationDatabase admin <interval> <count>

  • interval:刷新间隔(秒),默认 1。
  • count:输出次数,默认一直输出。

案例代码

bash 复制代码
# 连接到本地默认端口,每秒刷新一次
mongostat

# 输出示例:
# insert query update delete getmore command dirty used flushes vsize  res qr|qw ar|aw netIn netOut conn     time
#     *0    *0     *0     *0       0     1|0   0.0% 0.0%       0 1.00G 100M 0|0 0|0  157b   62k    4 10:30:01
#     12   345      2      1       5    23|0   1.2% 2.5%       1 1.01G 105M 0|0 0|0  245k  1.2m   12 10:30:02

# 每 2 秒刷新,共输出 10 次
mongostat 2 10

# 连接到分片集群的 mongos,查看整体统计
mongostat --host mongos.example.com --port 27017

# 监控特定数据库(通过 --discover 发现副本集所有节点)
mongostat --discover --host replicaSetName/localhost:27018,localhost:27019

# 输出 CSV 格式,方便导入 Excel(使用 --rowcount 和 awk 处理)
mongostat --rowcount 1 --noheaders | awk '{print $1","$2","$3}'

# 高精度监控(间隔 0.5 秒)
mongostat 0.5

关键输出字段详解

字段 含义
insert 每秒插入操作数
query 每秒查询操作数
update 每秒更新操作数
delete 每秒删除操作数
getmore 每秒 getmore(游标批量取数据)操作数
command 每秒执行命令数(如 findAndModify)
dirty WiredTiger 缓存中脏数据的百分比(过高说明写入压力大)
used WiredTiger 缓存正在使用的百分比
flushes 每秒执行 fsync 将数据刷新到磁盘的次数
vsize 进程使用的虚拟内存大小(GB)
res 常驻物理内存大小(GB)
`qr qw`
`ar aw`
netIn 每秒网络入站流量(字节)
netOut 每秒网络出站流量(字节)
conn 当前打开的连接数

4.3 mongotop + mongostat 联合使用场景

  • 定位慢集合 :用 mongotop 找到读写耗时高的集合,再用 mongostat 查看该时间点的整体负载。
  • 诊断突发流量 :观察到 queryinsert 突然飙升,检查是否有异常查询。
  • 监控复制延迟 :在副本集的 SECONDARY 节点运行 mongostat,观察 repl 字段(需要 --discover)。
  • 判断是否需要扩容 :长期 dirty > 20% 或 qr|qw > 0 表明资源不足。

案例代码(结合 shell 脚本):

bash 复制代码
#!/bin/bash
# 每10秒记录一次 mongostat 的快照,并提取关键指标
while true; do
  echo "=== $(date) ===" >> /var/log/mongo_monitor.log
  mongostat --rowcount 1 --noheaders | awk '{print "insert:"$1,"query:"$2,"dirty:"$11,"conn:"$17}' >> /var/log/mongo_monitor.log
  sleep 10
done

总结

本章系统介绍了四种监控 MongoDB 应用程序动态的关键技术:

  1. 查看当前操作 -- 通过 db.currentOp() 实时捕获正在执行的操作,并可 killOp() 终止异常任务。
  2. 使用系统分析器 -- 开启 profiling 记录慢查询和所有操作,利用 system.profile 集合分析性能瓶颈。
  3. 计算大小 -- 使用 Object.bsonsize()db.collection.stats()db.stats() 精确评估文档、集合、索引和数据库的磁盘占用,并通过 compact 回收碎片。
  4. 使用 mongotop 和 mongostat -- 命令行工具实时监控集合级读写耗时和服务器级吞吐量、缓存、队列等核心指标。

掌握这些工具和方法,能够帮助 DBA 和开发人员快速了解数据库动态,及时发现并解决性能问题,确保 MongoDB 生产环境稳定高效运行。在实际工作中,建议结合 Prometheus + Grafana 等监控系统实现可视化告警,并与本章的手动诊断手段互为补充。

相关推荐
oradh2 小时前
Oracle数据类型概述(一)
数据库·oracle·oracle基础·oracle入门基础·oracle数据类型
CheerWWW2 小时前
C++学习笔记——函数指针、Lambda表达式、谨慎使用using namespace std、命名空间
c++·笔记·学习
小兜全糖(xdqt)2 小时前
Ubuntu22.04安装最新版本redis
数据库·redis·缓存
@小博的博客2 小时前
【Linux探索学习】第六弹:操作系统的概念及冯诺依曼体系结构
linux·学习
talen_hx2962 小时前
《零基础入门Spark》学习笔记 Day 14
大数据·笔记·学习·spark
Clarence Liu2 小时前
langchain源码研究 - deepagents设计思想学习
人工智能·驱动开发·学习·langchain
Shadow(⊙o⊙)2 小时前
static与extern使用
c语言·学习
风曦Kisaki2 小时前
Linux服务Day03:自定义YUM仓库、网络YUM仓库(HTTP/FTP)、MariaDB数据库基础操作
linux·网络·数据库
weixin_704266052 小时前
redis 的集群
java·数据库·redis