事务中分页应避免sort+skip+limit,改用游标分页+严格复合索引;$or需为各分支建独立索引;部分索引须完全匹配查询条件才生效。

事务里不能用 sort + skip + limit 组合做分页?
不是不能,而是极不推荐。MongoDB 事务中执行 find().sort().skip().limit() 时,skip() 会强制扫描前 N 条匹配文档(哪怕只返回 10 条),导致事务内锁持有时间拉长、内存暴涨、甚至触发 maxTransactionLockRequestTimeoutMS 超时。更关键的是:事务中的查询无法利用覆盖索引跳过文档读取——因为事务必须保证隔离性,所有读操作默认走 snapshot 隔离,totalDocsExamined 很难降下来。
如何让事务内分页查询真正走覆盖索引
核心是放弃 skip(),改用「游标分页(cursor-based pagination)」+「严格复合索引」。这要求你把排序字段和分页锚点都纳入索引,并确保查询条件能精确命中索引前缀。
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
- 假设你要在事务中查
{ status: "active", category: "electronics" }下按created_at倒序分页,且只返回name和price - 必须建索引:
db.collection.createIndex({ status: 1, category: 1, created_at: -1, name: 1, price: 1 }) - 首次查询用:
db.collection.find({ status: "active", category: "electronics" }, { name: 1, price: 1, _id: 0 }).sort({ created_at: -1 }).limit(10) - 后续请求带游标:
db.collection.find({ status: "active", category: "electronics", created_at: { $lt: <last_seen_created_at> } }, { name: 1, price: 1, _id: 0 }).sort({ created_at: -1 }).limit(10)</last_seen_created_at> - 务必用
explain("executionStats")验证:totalDocsExamined === 0且nReturned等于实际返回数——这才是真覆盖
$or 查询在事务里怎么避免全表扫
事务中 $or 更危险:每个分支若没独立索引,整个查询退化为 COLLSCAN,且事务会锁住所有扫描过的文档。MongoDB 不会在事务里为 $or 自动选多个索引合并,它只挑一个(通常是第一个能用的),其余分支全靠 FETCH + FILTER。
- 不要建大而全的复合索引去“覆盖”所有
$or分支,比如{ a: 1, b: 1, c: 1, d: 1 }对{ $or: [{a:1,b:2}, {c:3,d:4}] }几乎无效 - 正确做法是为每个分支建最小够用的独立索引:
{ a: 1, b: 1 }和{ c: 1, d: 1 } - 用
explain("executionStats")检查executionStages下每个$or子节点是否都有IXSCAN;如果出现COLLSCAN或FETCH,说明该分支没走索引 - 事务中慎用
$expr—— 它直接绕过所有索引,totalDocsExamined必然等于集合总文档数
为什么部分索引(partial index)在事务分页中常被忽略
高频分页查询往往只针对活跃数据子集(如 status: "active"),但多数人建的是全量复合索引,导致索引体积大、内存占用高、更新成本上升。部分索引能精准收缩索引范围,但它在事务中有个硬限制:索引的 partialFilterExpression 必须与事务内查询条件完全匹配,否则不生效。
- 例如建了
db.collection.createIndex({ created_at: -1 }, { partialFilterExpression: { status: "active" } }) - 事务中查询必须显式带上
{ status: "active" },且不能是$in: ["active", "pending"]或$ne: "inactive"—— 后两者不满足 partial filter 的等值匹配语义 - 用
db.collection.getIndexes()确认索引带"partialFilterExpression"字段;再用explain()看indexName是否命中该索引名 - 注意:部分索引不支持在
_id上定义 filter,且$text、$geo索引不能设 partial
totalDocsExamined 在事务中比普通查询更敏感——它直接关联到 WAL 日志大小和主从同步延迟。**










