必须确认date字段为bson date类型,字符串存储会导致$gte/$lte按字典序比较而失效;建索引前需用$db.collection.findone()验证类型,若为字符串须用$datefromstring批量转换,再创建单字段升序索引或等值字段在前、范围字段在后的复合索引。

日期范围查询慢,八成是因为没给 date 字段建索引,或者建了但字段类型不对——比如存的是字符串,索引再好也白搭。
确认 date 字段是否为 BSON Date 类型
这是最常被跳过的一步。如果数据库里存的是 "2023-06-22" 这种字符串,$gte/$lte 查询会做字典序比较,结果完全不可信。
- 用
db.collection.findOne()查一条数据,看字段值是ISODate("2023-06-22T00:00:00.000Z")还是"2023-06-22" - 如果是字符串,不能靠加索引修复,必须先批量更新字段类型:用
$dateFromString聚合或应用层重写 - Mongoose 用户注意:Schema 中该字段必须声明为
Date类型,且插入时传new Date(),别传字符串
为单个 date 字段创建升序索引
对纯时间范围查询(如 { createdAt: { $gte: start, $lte: end } }),最直接有效的是单字段升序索引。
- 命令:
db.collection.createIndex({ createdAt: 1 }) - 升序/降序对范围查询影响不大,但升序更通用(也兼容
sort({ createdAt: 1 })) - 避免用
{ createdAt: -1 }单独建索引——除非你 90% 的查询都带sort({ createdAt: -1 })且不带范围条件 - 不要加
background: true以外的选项(如unique、sparse),除非业务明确需要
复合索引中 date 字段的位置很关键
当查询同时含等值条件和时间范围时(如 { status: "done", createdAt: { $gte: start } }),date 字段不能放在索引最前面。
- 按 ESR 原则:等值字段(
status)放前,排序字段(可选)居中,范围字段(createdAt)放最后 - 正确示例:
db.collection.createIndex({ status: 1, createdAt: 1 }) - 错误示例:
db.collection.createIndex({ createdAt: 1, status: 1 })—— 这个索引对{ status: "done" }+ 范围查询基本无效 - 如果还要按时间倒序取最新 N 条,可改成
{ status: 1, createdAt: -1 },MongoDB 能同时满足过滤+倒序
覆盖查询时 projection 必须严格匹配索引字段
想让 MongoDB 完全只查索引不读文档(即“覆盖查询”),投影字段必须全部来自索引,且不能用 $expr 或其他计算逻辑。
- 例如索引是
{ userId: 1, createdAt: 1, amount: 1 },那么find({ userId: 123 }, { userId: 1, createdAt: 1, amount: 1, _id: 0 })才可能被覆盖 - 哪怕多投一个
_id(默认包含),或少投一个索引字段,都会触发文档回表 - 用
.explain("executionStats")检查totalDocsExamined是否为 0;不是 0 就没被覆盖
真正容易被忽略的不是语法,而是字段类型一致性——索引建得再规范,只要库里混着字符串日期,查询就永远在碰运气。











