了解应用程序的动态
在 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 应用程序动态的关键技术:
- 查看当前操作 -- 通过
db.currentOp()实时捕获正在执行的操作,并可killOp()终止异常任务。 - 使用系统分析器 -- 开启 profiling 记录慢查询和所有操作,利用
system.profile集合分析性能瓶颈。 - 计算大小 -- 使用
Object.bsonsize()、db.collection.stats()、db.stats()精确评估文档、集合、索引和数据库的磁盘占用,并通过compact回收碎片。 - 使用 mongotop 和 mongostat -- 命令行工具实时监控集合级读写耗时和服务器级吞吐量、缓存、队列等核心指标。
掌握这些工具和方法,能够帮助 DBA 和开发人员快速了解数据库动态,及时发现并解决性能问题,确保 MongoDB 生产环境稳定高效运行。在实际工作中,建议结合 Prometheus + Grafana 等监控系统实现可视化告警,并与本章的手动诊断手段互为补充。