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

了解应用程序的动态

在 MongoDB 生产环境中,了解数据库当前正在执行的操作、资源消耗、数据大小以及实时吞吐量是性能调优和故障排查的基础。本章详细介绍四种关键方法:查看当前操作、使用系统分析器、计算集合/文档/索引大小,以及使用 mongotop 和 mongostat 命令行工具。所有案例基于 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:写操作耗时
    高 read 或 write 值表示该集合负载较重,可能需要优化查询或索引。

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 查看该时间点的整体负载。
  • 诊断突发流量 :观察到 query 或 insert 突然飙升,检查是否有异常查询。
  • 监控复制延迟 :在副本集的 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 等监控系统实现可视化告警,并与本章的手动诊断手段互为补充。

相关推荐
这个DBA有点耶29 分钟前
时序数据库InfluxDB高基数问题深度拆解:内存索引膨胀机制与OOM完整链路
数据库·架构·dba
加油yx43 分钟前
MySQL数据类型
数据库·mysql
li星野1 小时前
【学习记录】PCB与PCBA核心知识全解析:从设计到制造的关键细节
学习·制造
蓝胖的四次元口袋1 小时前
Redis知识梳理(1)
数据库·redis·缓存
夜雪一千1 小时前
MySQL 数据加密实战:从传输、存储到字段级加密,分清场景不踩坑
数据库·mysql
卿卿的产品经理日记2 小时前
【AI产品经理实战】Day 35|从“沉迷工具“到回归 AI 底层:Skill 收官、系统复盘与下一个 13 天
人工智能·学习·产品经理
wefg12 小时前
【Redis】C++ 自定义客户端
数据库·redis·缓存
这个DBA有点耶2 小时前
为什么UPDATE比SELECT危险得多?锁范围等于扫描行数
数据库·架构·代码规范
一条泥憨鱼2 小时前
【从0开始学习计算机网络】| ARP 协议:地址解析,ARP 欺骗,链路层寻址
网络·后端·学习·计算机网络
天国梦3 小时前
国密门禁品牌怎么选?2026年最新3个避坑要点
运维·服务器·数据库·python