MongoDB内部存在一种特殊的BSON类型叫Timestamp,它和日常业务里常用的Date类型不是一回事。Timestamp类型主要由复制集机制使用,最典型的例子就是oplog,每一条操作日志都带有一个ts字段,其中高32位是秒级Unix时间戳,低32位是操作序号。如果你在做oplog分析、延迟监控或者数据同步审计,就一定会碰到从Timestamp里取出秒数的需求。MongoDB 5.0为此专门提供了$tsSecond操作符,让这件事变得非常直接。本文将围绕它的用法、适用场景和常见误区展开。

一、$tsSecond的基本语法与返回值
$tsSecond的语法非常简单,它接受一个表达式作为参数,该表达式解析后必须是一个BSON Timestamp类型的值,返回值是其中高32位所代表的秒级时间戳,类型是long。基本写法如下:
db.oplog.aggregate([
{
$project: {
seconds: { $tsSecond: "$ts" },
increment: { $tsInc: "$ts" }
}
}
])
假设某条oplog记录的ts字段值为Timestamp(1718000000, 5),上面的管道会输出seconds为NumberLong(1718000000),increment为NumberLong(5)。可以看到$tsSecond经常和$tsInc搭配使用,后者取出的是低32位的自增序号,两者组合起来就能完整还原一个Timestamp。需要注意的是,参数必须解析为Timestamp类型,如果你传入的是一个Date类型的字段,聚合会直接报错,报错信息大致是$tsSecond参数必须是Timestamp类型。这是新手最容易踩的第一个坑。
还要强调一点,返回值是秒而不是毫秒。这一点和JavaScript的Date.now()以及MongoDB里的ISODate毫秒语义完全不同。如果你拿到$tsSecond的结果后直接与毫秒时间戳做比较或相减,得出的时间差会差一千倍。正确的做法是先乘以1000,或者使用配套的$tsMillisecond操作符。
二、把秒级时间戳转换成可读的日期
提取出秒数只是第一步,实际分析中我们往往需要把它变成人类可读的时间。MongoDB聚合管道提供了$dateFromParts操作符,可以接受一个毫秒级时间戳作为输入,配合$tsSecond乘以1000即可完成转换:
db.oplog.aggregate([
{
$project: {
ts: 1,
opTime: {
$dateFromParts: {
epochMillis: {
$multiply: [{ $tsSecond: "$ts" }, 1000]
}
}
}
}
}
])
上面的写法在MongoDB 5.0及更高版本中可用。如果你用的是5.0以后的版本,还可以直接用$tsMillisecond一步拿到毫秒级时间戳,写法更简洁:
db.oplog.aggregate([
{
$project: {
opTime: {
$dateFromParts: {
epochMillis: { $tsMillisecond: "$ts" }
}
}
}
}
])
两种写法结果完全一致。区别在于$tsSecond保留了原始的秒级语义,在与外部系统的秒级时间戳对接时更直观,而$tsMillisecond省去了乘法运算。另外提醒一下,$tsMillisecond同样只在5.0及以上版本提供,低版本只能使用$tsSecond加$multiply的组合。
三、实战场景:分析oplog中的操作时间分布
$tsSecond最常见的用武之地就是oplog分析。下面这个例子统计local库oplog.rs集合中每小时的操作数量,用于观察写入高峰:
db.getSiblingDB("local").oplog.rs.aggregate([
{
$group: {
_id: {
$dateTrunc: {
date: {
$dateFromParts: {
epochMillis: {
$multiply: [{ $tsSecond: "$ts" }, 1000]
}
}
},
unit: "hour"
}
},
count: { $sum: 1 },
ops: { $addToSet: "$op" }
}
},
{ $sort: { _id: 1 } }
])
这个管道先用$tsSecond取出秒,乘以1000转成毫秒,再通过$dateFromParts还原为Date对象,接着用$dateTrunc按小时截断分组。输出的count就是每个小时的oplog写入条数,ops则列出该时段出现过的操作类型(i表示插入,u表示更新,d表示删除)。
另一个典型场景是计算复制延迟。在从节点上执行管道,取出oplog最后一条记录的$tsSecond,与主节点当前时间的秒数做差,就能得到一个粗略的延迟估计。相比直接比较ts字段,这种做法的数值更便于阅读和上报到监控系统。如果你的监控体系按秒粒度采集,直接使用$tsSecond的原始输出反而是最省事的方案,省去了来回换算的麻烦。
四、与替代方案的对比及注意事项
有人可能会问,为什么不用$toDate直接转?原因是$toDate同样只接受Timestamp以外的类型会有限制,而且即便能转换,你拿到的是完整Date对象,如果后续要和其他秒级时间戳做数值运算,还得再用$toLong转回来,绕了一圈。用$tsSecond的好处是语义明确,从Timestamp取秒,就是字面意思,没有任何隐式行为。
还有几种需要留意的边界情况。第一,普通业务集合中的字段基本都是Date类型,不要想当然地对它们使用$tsSecond,会直接报错;判断字段类型可以用$type表达式先做过滤。第二,Timestamp类型的取值范围由两个32位整数构成,秒部分在2038年会溢出到另一个区间吗?实际上BSON Timestamp由64位存储,$tsSecond返回的long可以安全容纳,不必担心2038问题。第三,$tsSecond属于聚合表达式,只能在聚合管道(包括$project、$match配合expr等位置)中使用,不能直接用于普通查询的find投影语法。
总结一下:当你处理oplog、change stream中的clusterTime这类Timestamp字段时,$tsSecond是最标准的取秒方式;搭配$tsInc可以还原完整序号,搭配$dateFromParts或$tsMillisecond可以转成日期。只要记住它只认Timestamp类型、返回的是秒不是毫秒这两点,就能在时间分析类管道中用得得心应手。
MongoDB聚合管道$tsSecond时间戳修改时间:2026-09-04 16:12:39