MongoDB排序必须前置匹配且字段走索引,否则触发内存排序导致性能崩溃;正确做法是$match在$sort前并命中复合索引,配合$project精简字段、$limit控制数据量,用explain验证执行计划。
排序前必须有匹配,且字段得走索引
没 $match 就直接 $sort,MongoDB 很可能全表扫描再内存排序,一过百万文档就卡死或报 Sort exceeded memory limit。关键不是“能不能排”,而是“用什么排”。$sort 能否利用索引,完全取决于它前面有没有能命中索引的 $match,且该 $match 必须在 $sort 之前、且字段顺序匹配复合索引定义。
- 错误写法:
{$sort: {createdAt: -1}}单独放在开头 → 强制内存排序 - 正确写法:
{$match: {status: "completed", createdAt: {$gte: ISODate("2026-01-01")}}}+{$sort: {createdAt: -1}}→ 若已有{status: 1, createdAt: -1}索引,可索引扫描+索引排序 - 注意:
$regex开头通配(如^abc)无法走索引;$text或$expr条件也基本不参与排序优化
$project 不精简,$sort 就扛不住
排序时每个文档都要加载进内存比较,字段越多、单文档越大,内存压力越陡峭。尤其当 $unwind 或 $lookup 后文档数量暴增,但 $project 还保留原始嵌套对象、base64 字段、长文本日志,$sort 阶段实际搬运的是几十 KB 的垃圾数据。
- 务必在
$sort前加$project,只留排序和后续需要的字段,例如:{$project: { _id: 0, name: 1, score: 1, createdAt: 1 }} - 避免
$project中用$cond或$ifNull计算新字段后再排序——这些字段无法被索引加速,纯 CPU 比较 - 对数组字段,先
$slice或$filter再$project,别让未裁剪的数组拖慢排序
千万级数据别硬排,用 $limit + 复合索引控制输入量
真实业务里,用户几乎从不需要“把全部千万条记录排完”。强行全量排序既慢又危险。更稳妥的做法是:靠索引把输入控制在万级以内,再排。
用Apache Spark进行大数据处理
本文档主要讲述的是用Apache Spark进行大数据处理——第一部分:入门介绍;Apache Spark是一个围绕速度、易用性和复杂分析构建的大数据处理框架。最初在2009年由加州大学伯克利分校的AMPLab开发,并于2010年成为Apache的开源项目之一。 在这个Apache Spark文章系列的第一部分中,我们将了解到什么是Spark,它与典型的MapReduce解决方案的比较以及它如何为大数据处理提供了一套完整的工具。希望本文档会给有需要的朋友带来帮助;感
下载
- 给高频排序场景建复合索引,等值字段在前、范围/排序字段在后,例如:
db.orders.createIndex({ status: 1, createdAt: -1 }) - 配合
$match+$limit先筛出目标批次:{$match: {status: "shipped"}}, {$limit: 5000}, {$sort: {createdAt: -1}} - 如果必须分页,慎用
$skip:跳过 10 万条再取 20 条,MongoDB 仍要遍历前 10 万条。改用“游标分页”(记录上一页最后的createdAt值)
explain("executionStats") 是唯一可信依据
所有“应该快”的判断,都得被 explain 打脸过才算数。尤其要注意两个指标:
-
totalDocsExamined应接近nReturned,差距越大说明索引没生效或条件写错 -
executionStages.stage里出现SORT节点且memUsage超 32MB,就是内存排序警报 - 执行计划里若出现
IXSCAN但后面紧跟着SORT,说明索引支持了过滤,但没支持排序——得检查索引字段顺序是否匹配$sort
管道阶段之间的数据膨胀常被忽略:比如 $unwind 把 1 个含 100 项的订单展开成 100 个文档,后面却没 $project 控制字段,那 $sort 实际处理的是 100 倍体积的数据流。