监控告警脚本想快速获取MongoDB某个库的存储水位,通常思路是调用一个命令或者单独实现一套统计逻辑。聚合管道中的$dbStats阶段提供了一种更直接的方式,它可以在聚合查询内部返回数据库级别的统计数据,不需要在应用里拼接额外的管理命令。这个阶段适合做容量巡检、自动化运维和自定义监控面板,也容易跟现有的聚合代码风格保持一致。

$dbStats 的语法与可选参数
$dbStats作为聚合管道的一个阶段,基本写法是把一个对象放在阶段位置,对象里用scale和freeStorage两个可选字段控制输出。下面是在inventory库上执行、并把字节数换算成MB的示例:
db.getSiblingDB('inventory').aggregate([
{ $dbStats: { scale: 1024 * 1024 } }
])
scale参数的默认值是1,也就是说默认所有字节相关的字段都以字节为单位返回。设置为1024会把结果换算成KB,设置为1024乘1024则会换算成MB。freeStorage参数接受布尔值,默认是false,只有当它被设为true时,返回文档才会包含fsUsedSize和fsTotalSize这两个文件系统级别的字段。需要注意的是,freeStorage能否生效受存储引擎和权限影响,在WiredTiger引擎下通常可以拿到,但执行账号必须拥有对应权限。
另一个容易忽略的细节是,$dbStats不能像$match那样放在管道中间去过滤集合,它本身就是统计当前数据库的整体状态。如果在同一个管道里先写其他阶段再写$dbStats,前面的文档处理对它没有意义;实际使用时一般把$dbStats放在第一个阶段,或者配合$facet把统计结果与集合查询结果组合起来。
返回值字段解析
一次最简单的$dbStats调用会返回类似下面这样的文档:
{
db: 'inventory',
collections: 12,
views: 0,
objects: 18342,
avgObjSize: 518.22,
dataSize: 9.07,
storageSize: 11.43,
indexes: 20,
indexSize: 2.16,
totalSize: 13.59,
scaleFactor: 1048576
}
其中collections表示当前数据库里集合的数量,views表示视图数量,objects则是集合中所有文档的总数。这里要注意objects统计的是所有普通集合里的文档,不包含视图内部从源集合派生的行数。avgObjSize是平均每个文档的逻辑大小,它由统计信息计算得出,可以用作容量趋势判断的粗略参考,但不要把它当成精确的磁盘分配依据。
dataSize和storageSize是两个最值得关注的字节字段。dataSize表示所有文档和集合元数据在未压缩情况下的逻辑数据量,类似业务数据本身的体积;storageSize表示MongoDB在磁盘上为集合分配的空间,通常大于dataSize,因为存储引擎会预留空间、做压缩后还会产生块分配粒度。两者差距越大,说明空间放大越明显,可能需要整理数据或改用更合适的存储策略。对于启用了压缩的场景,storageSize理论上可能小于dataSize,但这取决于数据类型和压缩算法。
indexes和indexSize分别代表索引数量和索引占用的磁盘空间。totalSize等于storageSize与indexSize之和,它更贴近人们对一个数据库磁盘占用的直观理解。如果把freeStorage设为true,返回里还会出现fsUsedSize和fsTotalSize,它们反映数据库所在文件系统的使用量和总容量。
与 db.stats() 的差异及驱动集成
熟悉MongoDB命令行的人会发现,$dbStats的输出和db.stats()非常相似。二者本质上是同一套数据库统计信息的不同入口:db.stats()是shell方法,底层执行的是dbstats命令;$dbStats是聚合阶段,它在驱动层通过普通聚合API就能执行。这个区别在应用代码里体现得很明显,比如在Python驱动中,可以像下面这样直接拿到统计结果:
from pymongo import MongoClient
client = MongoClient('mongodb://127.0.0.1:27017')
stats = list(client['inventory'].aggregate([
{ '$dbStats': { 'scale': 1024 * 1024 } }
]))
print(stats[0]['totalSize'])
如果使用db.stats(),在驱动里通常要调用database.command(),代码结构会更偏向管理命令风格;而使用$dbStats则能把数据库统计放进统一的聚合管道处理链路中,甚至可以用$facet在一次查询里同时返回业务数据和数据库统计。下面是一个组合示例,业务查询只取最近一条记录,同时通过$facet把统计结果放在一个字段里:
db.getSiblingDB('inventory').aggregate([
{
$facet: {
latestOrder: [
{ $sort: { createdAt: -1 } },
{ $limit: 1 }
],
dbStats: [
{ $dbStats: { scale: 1024 * 1024 } }
]
}
}
])
这样一次往返就能同时获得业务数据和库级别统计,适合在管理后台首页或诊断接口中减少请求次数。需要注意的是,$facet中的子管道彼此独立,$dbStats不会受另一个子管道的影响,这对统计结果的一致性反而有利。
权限、分片与性能注意事项
执行$dbStats需要当前账号具备相应的数据库统计权限。单机或副本集环境中,通常需要拥有目标数据库的dbStats权限动作,或者集群级别的clusterMonitor角色。如果请求freeStorage,还需要额外权限来访问文件系统统计信息。账号权限不足时,聚合会直接返回未授权错误,而不是静默缺失字段,所以监控账号最好单独分配最小权限,避免为了统计而给过大的管理角色。
在分片集群中,$dbStats的行为和单机不同。通过mongos执行该阶段时,它不会自动汇总所有分片的数据库统计;要对分片集群做完整的容量分析,应分别连接各个分片的mongod节点执行,或者在每个分片上进行聚合后由应用侧合并结果。如果只是大概看路由层的元数据,结果会不完整,这一点在监控架构设计时尤其需要提前确认。
性能方面,$dbStats本质上是从存储引擎和文件系统读取统计信息,通常响应很快,不会扫描集合内容。但在集合数量非常多、索引元数据庞大的数据库中,首次执行仍可能产生一定开销。建议监控任务采用较低频率轮询,例如每分钟或每五分钟一次,并把scale设成较大单位,避免输出过多小数位。把freeStorage保持为false也能减少不必要的文件系统调用。