$sample本身不慢,慢是因为未加$match过滤、未建索引或未限制采样前数据量;应先$match缩小候选集并建对应索引,或改用$rand+limit替代。

直接说结论:$sample 不慢,慢是因为你没加 $match 过滤、没建索引、或者在超大集合上没限制采样前的数据量。
为什么 $sample 在某些情况下会变慢
很多人一执行 db.collection.aggregate([{$sample: {size: 1}}]) 就卡住,不是 $sample 本身有问题,而是它默认要扫描整个集合来“公平随机”。MongoDB 的 $sample 实现原理是:先随机跳过一部分文档,再取一条;但如果集合没索引、没过滤条件,它就得从头开始遍历,直到找到满足随机偏移的那条——这在千万级集合上可能扫几百万文档才命中。
- 集合无任何查询条件时,
$sample等价于全表扫描 + 随机偏移,docsExamined接近总文档数 - 未对
$sample前的阶段做数据裁剪,比如漏掉$match,导致后续所有操作都在庞大数据集上运行 - 在副本集或分片集群中,
$sample无法下推到分片层并行执行(v6.0+ 支持部分优化,但仍有约束)
必须搭配 $match 使用,且优先走索引
$sample 不能 standalone 工作。它前面的管道阶段越轻,整体越快。最有效的写法永远是先用 $match 缩小候选集,再抽样:
db.collection.aggregate([
{$match: {status: "active", created_at: {$gte: ISODate("2025-01-01")}}},
{$sample: {size: 1}}
])
- 确保
$match中的字段有索引,例如{status: 1, created_at: 1}复合索引 - 避免在
$match中使用正则、$where、$text等无法走索引的操作 - 如果业务允许,把“活跃/有效”状态作为前置过滤条件,比后期筛更省资源
替代方案:用 findOne() + 随机排序(仅限中小集合)
当集合总文档数稳定在百万以内,且你只需要单条,sort({$natural: 1}).limit(1) 或 sort({$rand: 1}).limit(1)(4.4+)反而更稳:
db.collection.find({status: "active"}).sort({$rand: 1}).limit(1)
-
$rand是服务器端真随机排序,比$natural更可靠($natural依赖插入顺序,易被预测) - 这个操作仍需走索引支持
$match阶段,否则sort会触发内存排序(maxSortKeyMemoryUsageBytes超限就报错) - 注意:
$rand无法和$skip组合用于分页式随机,它只适合“任意一条”场景
容易被忽略的坑:游标超时与连接中断
随机查询慢还可能不是数据库问题,而是客户端行为导致:
- mongosh 或驱动默认游标超时是 10 分钟,但大集合抽样可能耗时波动大,建议显式加
.maxTimeMS(30000) - Node.js 驱动中若用
collection.aggregate(...).toArray(),会一次性拉取全部结果——哪怕只想要 1 条,也得等整个 pipeline 完成 - 真正只需 1 条时,应改用
collection.aggregate(...).next()或findOne()形式,避免构建完整游标
复杂点在于:$sample 的性能不是孤立指标,它和你的数据分布、索引覆盖度、读写负载强相关。没有“开箱即用”的随机查询,只有“结合业务约束定制的采样策略”。


















