use <db>把当前数据库上下文切到 <db>。库是惰性创建的,第一条文档写入后才会出现在 show dbs 里。
use myapp
use admin -- 切到管理库
MongoDB 命令速查,80+ 条 shell 命令和查询操作符,覆盖 CRUD、聚合、索引、复制集。
use <db>把当前数据库上下文切到 <db>。库是惰性创建的,第一条文档写入后才会出现在 show dbs 里。
use myapp
use admin -- 切到管理库
show dbs / show collections列出所有库,或当前库下所有集合。show dbs 不显示空库,刚 use 但没写过的库不会出现。
show dbs
show collections
show users -- 当前库里的用户
db.help() / db.<coll>.help()打印 db 对象或某个集合上可用的方法。不离开 shell 就能查 API 的最便宜方式。
db.help()
db.users.help()
db.users.find().help() -- cursor 上的方法
db.<coll>.stats()返回某集合的存储统计:文档数、大小、平均文档大小、索引总大小、每个索引大小。热集合排查的第一步。
db.orders.stats()
db.orders.stats({scale: 1024 * 1024}) -- 以 MB 显示db.serverStatus()返回服务端整体统计:连接数、opcounters、内存、网络、复制延迟、WiredTiger 缓存。最有用的诊断命令。
⚠ 常见坑: 繁忙集群一次返回几百 KB JSON。只取你需要的 section:db.serverStatus().connections、.wiredTiger.cache、.repl。
db.serverStatus().connections
db.serverStatus().wiredTiger.cache
db.serverStatus().repl
db.version() / db.hostInfo()返回 MongoDB 服务端版本,或详细的主机信息(OS、内核、CPU、内存)。应用启动时记下来,方便提工单。
db.version() -- "7.0.5"
db.hostInfo().os
db.hostInfo().system
db.runCommand({...})用原始文档形式直接执行任意数据库命令。最底层的接口,所有 helper(createIndex、collMod 等)底下都是 runCommand。
db.runCommand({ping: 1})db.runCommand({collStats: "orders"})db.runCommand({listIndexes: "orders"})db.<coll>.drop() / db.dropDatabase()删除某个集合(含索引),或删除整个当前库。没有近期备份/快照就不可逆。
⚠ 常见坑: 没有二次确认。开错 shell tab 上一句 db.dropDatabase() 就把生产清掉。打 drop 前再看一眼 use 那一行。
db.tmp_import.drop()
db.dropDatabase() -- 当前库整个删
db.getCollectionNames() / db.getCollectionInfos()getCollectionNames() 返回集合名的字符串数组;getCollectionInfos() 返回完整元数据(类型、选项、视图定义、UUID)。区分视图和真集合用后者。
db.getCollectionNames()
db.getCollectionInfos({type: "view"}) // 只列视图db.adminCommand({...})不管当前 db 上下文,直接对 admin 库执行命令。listDatabases、getParameter、setParameter、currentOp 这类集群级命令必须走它。
db.adminCommand({listDatabases: 1})db.adminCommand({getParameter: 1, featureCompatibilityVersion: 1})db.adminCommand({setParameter: 1, logLevel: 2})db.currentOp() / db.killOp(opid)currentOp() 列出服务端正在跑的操作;killOp(opid) 按 opid 终止一个。生产上抓并杀掉失控查询的第一现场工具。
⚠ 常见坑: killOp 一个持锁的写不会立刻回滚,它在下一个安全点中断。长跑的 $out 或建索引在 killOp 后可能还要几秒才真停下。
db.currentOp({"secs_running": {$gte: 5}, "op": "query"})db.killOp(12345)
db.<coll>.renameCollection("newName")在同一库内重命名集合(原子元数据操作,不拷数据)。要覆盖已存在的同名集合传 {dropTarget: true}。
⚠ 常见坑: renameCollection 不能跨库移动,也不能用于已分片集合。跨库搬运只能 dump 后 restore。
db.orders_2026.renameCollection("orders")db.staging.renameCollection("orders", {dropTarget: true})db.createCollection(name, options)显式建集合并设置那些插入时无法指定的选项:capped 大小、schema 校验器、时序配置、默认 collation。
db.createCollection("logs", {capped: true, size: 104857600, max: 100000})db.createCollection("metrics", {timeseries: {timeField: "ts", metaField: "tags", granularity: "seconds"}})db.runCommand({collMod: "coll", ...})原地改集合选项:换 schema 校验器、设 validationLevel/validationAction、调 TTL 的 expireAfterSeconds,或隐藏/取消隐藏索引而不删它。
db.runCommand({collMod: "sessions", index: {keyPattern: {created_at: 1}, expireAfterSeconds: 7200}})db.runCommand({collMod: "users", validationAction: "warn"})db.fsyncLock() / db.fsyncUnlock()fsyncLock() 把待写刷盘并阻塞后续所有写,用于做一致的文件系统快照备份。fsyncUnlock() 解锁、恢复写入。
⚠ 常见坑: 忘了解的 fsyncLock 会静默阻塞该节点上所有写。务必在 finally 里配 fsyncUnlock,繁忙生产集的 primary 上绝不要跑它。
db.fsyncLock()
db.fsyncUnlock()
db.<coll>.insertOne(doc)插入一条文档。缺 _id 自动生成 ObjectId。返回 {acknowledged, insertedId}。受集合写关注配置(5.0+ 默认 w: "majority")影响。
db.users.insertOne({name: "Alice", age: 30, tags: ["admin"]})db.users.insertOne({_id: "u-1001", name: "Bob"})db.<coll>.insertMany([doc, ...])一次 round-trip 批量插入文档数组。默认 ordered,遇错即停。要"出错继续、最后汇报"传 {ordered: false}。
⚠ 常见坑: ordered: false 能大幅加速批量导入,但并不会让重复 _id 错误消失,失败的文档会被收集,最后一并通过 BulkWriteError 报出。
db.users.insertMany([{name: "Alice"}, {name: "Bob"}])db.users.insertMany(docs, {ordered: false}) -- 批量导入推荐db.<coll>.find(filter, projection)返回匹配文档的 cursor。filter 是匹配文档,projection 限制返回字段。空 filter {} 匹配全部。
⚠ 常见坑: find() 不加 limit 在千万级集合上把所有数据流回客户端。应用里务必配 .limit(N);mongo shell 自动截到 20,driver 不会。
db.users.find({age: {$gte: 18}})db.users.find({}, {name: 1, email: 1, _id: 0}) -- 投影db.users.find({status: "active"}).sort({created_at: -1}).limit(20)db.<coll>.findOne(filter, projection)返回第一条匹配文档(无则 null),而不是 cursor。等价于 find().limit(1).next(),单条记录查找语法更顺。
db.users.findOne({_id: ObjectId("65...")})db.users.findOne({email: "alice@x.io"}, {password_hash: 0})db.<coll>.updateOne(filter, update, options)更新第一条匹配文档。update 必须用操作符($set、$inc、$push),直接字段赋值是替换,不是更新。"没就建"加 {upsert: true}。
⚠ 常见坑: 忘了 $set 是头号错误:db.users.updateOne({_id: x}, {name: "Alice"}) 是把整条文档替换成 {_id: x, name: "Alice"},其它字段全没。一定要 $set。
db.users.updateOne({_id: "u-1001"}, {$set: {age: 31}})db.counters.updateOne({name: "page_views"}, {$inc: {n: 1}}, {upsert: true})db.users.updateOne({_id: "u-1001"}, {$push: {tags: "vip"}})db.<coll>.updateMany(filter, update, options)把 update 应用到所有匹配文档。操作符规则同 updateOne。每条文档原子,但跨批不原子,中途崩溃会留下部分已更新。
db.users.updateMany({status: "trial"}, {$set: {status: "expired"}})db.orders.updateMany({region: "us-east"}, {$inc: {tax_cents: 100}})db.<coll>.replaceOne(filter, replacement)用新文档替换第一条匹配文档(保留 _id)。replacement 是普通文档,不带 $ 操作符。要局部更新用 updateOne。
db.users.replaceOne({_id: "u-1001"}, {name: "Alice", age: 31, tags: []})db.<coll>.findOneAndUpdate(filter, update, options)原子查到并更新一条文档,返回更新前或更新后快照。{returnDocument: "after"} 返回更新后。"读改一气呵成"的基石。
db.counters.findOneAndUpdate({_id: "order_id"}, {$inc: {seq: 1}}, {returnDocument: "after", upsert: true})db.jobs.findOneAndUpdate({status: "pending"}, {$set: {status: "running", worker: "w-1"}}, {sort: {priority: -1}, returnDocument: "after"})db.<coll>.findOneAndDelete(filter, options)原子查到并删除一条文档,返回被删的文档。配 sort + filter 天然实现"最多一次"任务队列。
db.jobs.findOneAndDelete({status: "pending"}, {sort: {priority: -1}})db.<coll>.deleteOne(filter) / deleteMany(filter)删除第一条或所有匹配文档。filter 写精确,deleteMany({}) 会把整个集合清掉。
⚠ 常见坑: deleteMany({}) 静默成功,会清掉所有。没有确认。"全删"用 drop() 更快(不写每条 oplog),两种都不可逆。
db.users.deleteOne({_id: "u-1001"})db.users.deleteMany({status: "expired", expires_at: {$lt: new Date()}})db.<coll>.bulkWrite([ops...], options)一次 round-trip 执行 insertOne / updateOne / updateMany / replaceOne / deleteOne / deleteMany 异构批操作。ETL 和迁移的首选原语。
⚠ 常见坑: 默认 ordered: true,一条失败整批停。批量加载传 {ordered: false} 继续跑;最后的 BulkWriteError 会列出每条失败 op。
db.users.bulkWrite([
{insertOne: {document: {name: "Alice"}}},
{updateOne: {filter: {_id: "u-1"}, update: {$set: {age: 30}}}},
{deleteOne: {filter: {status: "expired"}}}
], {ordered: false})db.<coll>.countDocuments(filter) / estimatedDocumentCount()统计匹配 filter 的文档数(精确,底下走聚合),或从集合元数据取快速整表计数(估算,忽略 filter)。
⚠ 常见坑: count()(不带 Documents 后缀)4.0 起 deprecated。countDocuments() 精确但 O(匹配数);大集合无索引会全扫。estimatedDocumentCount() O(1) 但写入期间可能不准。
db.users.countDocuments({status: "active"})db.users.estimatedDocumentCount() -- 快速整表计数
db.<coll>.distinct(field, filter)返回某字段在匹配文档中的去重值数组。响应总大小 16 MB,高基数场景改用聚合 $group。
db.users.distinct("country")db.users.distinct("country", {status: "active"})updateOne(f, {$unset: {field: ""}})从匹配文档里彻底删掉一个字段。字段名后面的值会被忽略,惯例写空串。这和设为 null 不同,设 null 还保留 key。
db.users.updateOne({_id: "u-1"}, {$unset: {temp_token: ""}})db.users.updateMany({}, {$unset: {legacy_field: ""}}) // 迁移后清字段updateOne(f, {$rename: {"old": "new"}})重命名匹配文档上的字段。点号支持嵌套路径。新名不能和文档里已有字段冲突。
⚠ 常见坑: $rename 不能跨数组元素,写不了 "items.$.sku"。要改数组元素字段名得读改写,或用聚合管道更新。
db.users.updateMany({}, {$rename: {"fullname": "name"}})db.users.updateMany({}, {$rename: {"address.zip": "address.postal_code"}})updateOne(f, {$addToSet: {tags: "x"}})只在值不存在时才加进数组,保证数组无重复、像集合。一次加多个值用 $each。
db.users.updateOne({_id: "u-1"}, {$addToSet: {tags: "vip"}})db.users.updateOne({_id: "u-1"}, {$addToSet: {tags: {$each: ["vip", "beta"]}}})$push with $each / $slice / $sort / $position完整的 $push 可一次追加多值($each)、限制数组长度($slice)、保持有序($sort)、或插到指定位置($position)。组合起来一次更新就维护一个有界有序的排行榜。
⚠ 常见坑: $slice 负数保留后 N 个,正数保留前 N 个。排行榜留前 10:降序 $sort 后 $slice: 10。
db.scores.updateOne({game: "g1"}, {$push: {top: {$each: [{u: "a", s: 90}], $sort: {s: -1}, $slice: 10}}})db.feed.updateOne({_id: "f1"}, {$push: {items: {$each: [x], $position: 0, $slice: 100}}})$pull / $pullAll / $pop$pull 删数组里所有匹配条件的元素;$pullAll 删列出的精确值;$pop 删第一个(-1)或最后一个(1)。数组删除三件套。
db.users.updateOne({_id: "u-1"}, {$pull: {tags: "expired"}})db.users.updateOne({_id: "u-1"}, {$pull: {scores: {$lt: 60}}}) // 按条件删db.feed.updateOne({_id: "f1"}, {$pop: {items: 1}}) // 删末尾updateOne(f, {$set: {"arr.$[el].done": true}}, {arrayFilters: [...]})过滤位置操作符 $[<id>] 更新所有匹配 arrayFilters 条件的数组元素。无需读改写、精准更新嵌套数组元素的现代写法。
⚠ 常见坑: $[](无标识符)的全位置操作符会更新数组里每个元素,容易和过滤式 $[<id>] 混淆。只改部分元素就用 $[<id>] + arrayFilters。
db.orders.updateOne({_id: "o-1"}, {$set: {"items.$[it].shipped": true}}, {arrayFilters: [{"it.sku": "abc"}]})db.users.updateMany({}, {$inc: {"scores.$[].value": 1}}) // 每个元素 +1updateOne(f, [{$set: {...}}]) // pipeline update聚合管道更新(4.2+)让 update 用表达式引用同一文档的其它字段,比如 total = price * qty。经典操作符做不到。
db.orders.updateMany({}, [{$set: {total: {$multiply: ["$price", "$qty"]}}}])db.users.updateMany({}, [{$set: {name: {$toLower: "$name"}}}]) // 引用自身字段db.<coll>.watch(pipeline, options) // change stream开 change stream,一个可恢复的 cursor,在副本集或分片集群上实时吐出 insert/update/replace/delete 事件。不轮询做实时同步的基石。
⚠ 常见坑: change stream 需要副本集(哪怕单节点),独立 mongod 上用不了。持久化 resumeToken,重启的消费者才能从断点接着读。
const cs = db.orders.watch([{$match: {operationType: "insert"}}])db.orders.watch([], {resumeAfter: token, fullDocument: "updateLookup"})findAndModify (legacy helper)findOneAndUpdate / findOneAndDelete 背后的老式单方法形式。一个文档里带 {query, update, remove, new, sort, upsert}。新代码优先用明确的 findOneAnd* helper。
db.counters.findAndModify({query: {_id: "seq"}, update: {$inc: {n: 1}}, new: true, upsert: true}){field: {$eq: value}}匹配 field 等于 value 的文档。等价于 {field: value};$eq 写法主要在 $elemMatch 和其它复合操作符里用。
db.users.find({age: {$eq: 30}})db.users.find({age: 30}) -- 等价{field: {$gt: v}} / $gte / $lt / $lte比较操作符。对数字、日期、字符串(字典序)、ObjectId(嵌入了创建时间戳)都生效。配 sort + 索引实现高效时间范围查询。
db.orders.find({total_cents: {$gte: 10000}})db.events.find({created_at: {$gte: ISODate("2026-05-01"), $lt: ISODate("2026-06-01")}})db.users.find({_id: {$gt: ObjectId.createFromTime(1716700000)}}){field: {$in: [v1, v2, ...]}}匹配 field 等于列表中任一值的文档。字段上有索引时会用上。最常用的多值查找。
⚠ 常见坑: $in 数组特别大(1 万+)每次匹配都吃内存,超过阈值索引也用不利索。大 ID 列表分页,或用 $lookup 配中间集合。
db.users.find({status: {$in: ["active", "trial"]}})db.products.find({_id: {$in: [ObjectId("..."), ObjectId("...")]}}){field: {$nin: [...]}} / {field: {$ne: v}}$nin 匹配 field 不在列表里的文档;$ne 匹配 field 不等于 value 的文档。都可能慢,用不上正向索引扫描,可能退化为全表扫。
⚠ 常见坑: $ne 和 $nin 也会匹配字段缺失的文档。要排除缺失字段的文档,组合写 {field: {$exists: true, $ne: v}}。
db.users.find({country: {$ne: "US"}})db.users.find({role: {$nin: ["admin", "owner"]}}){field: {$regex: /pattern/, $options: "i"}}用 JS 正则匹配。锚定前缀(/^foo/)能用上字段索引;不锚定或忽略大小写就用不上。
⚠ 常见坑: db.users.find({email: /alice/}) 千万级集合上即使 email 有索引也会全扫。加 ^ 锚定,或用 text 索引做全文检索。
db.users.find({email: /^alice@/}) -- 锚定,能用索引db.users.find({name: {$regex: "^al", $options: "i"}}){field: {$exists: true|false}}匹配字段存在(或不存在)的文档。$exists: true 也匹配显式设为 null 的字段。迁移期间稀疏 schema 常用。
db.users.find({deleted_at: {$exists: false}})db.users.find({phone: {$exists: true, $ne: null}}){field: {$type: "string"|"int"|"date"|...}}按 BSON 类型匹配。常用于查出某字段类型不对的文档(粗糙迁移后的烂摊子),或区分 int 和 double。
db.users.find({age: {$type: "string"}}) -- 找误存为字符串的年龄db.events.find({created_at: {$type: "date"}}){array_field: {$elemMatch: {a: 1, b: {$gt: 2}}}}匹配数组里"同一个元素"同时满足所有条件的文档。不带 $elemMatch 多个条件可能跨不同元素满足。
⚠ 常见坑: 常见 bug:db.users.find({"scores.subject": "math", "scores.value": {$gt: 90}}) 能匹配 math=70 + english=95 的用户。用 $elemMatch 把两个条件绑到同一个数组元素。
db.users.find({scores: {$elemMatch: {subject: "math", value: {$gt: 90}}}}){$or: [{...}, {...}]} / {$and: [...]}逻辑组合。$or 返回子查询并集;多字段并列时 $and 是隐式的,但同一字段上多条件必须显式 $and。
⚠ 常见坑: {age: {$gt: 18}, age: {$lt: 65}} 第一个会被静默丢弃,JS 对象 key 唯一。用 {$and: [{age: {$gt: 18}}, {age: {$lt: 65}}]} 或简写 {age: {$gt: 18, $lt: 65}}。
db.users.find({$or: [{status: "active"}, {created_at: {$gt: lastWeek}}]})db.users.find({age: {$gt: 18, $lt: 65}}) -- 同字段多条件简写{field: {$not: {$gt: 100}}}对单操作符表达式取反。和 $ne / $nin 一样,$not 也匹配字段缺失的文档,必要时配 $exists。
db.users.find({age: {$not: {$gt: 100}}}) -- 100 岁及以下或字段缺失{array_field: {$all: [v1, v2]}} / {$size: N}$all 匹配数组同时包含所有列出值的文档(顺序无关);$size 匹配长度恰为 N 的数组。$size 不支持范围,"≥ N" 写 {"array_field.N-1": {$exists: true}}。
db.posts.find({tags: {$all: ["mongo", "tutorial"]}})db.users.find({roles: {$size: 0}}) -- 没角色的用户{field: {$mod: [divisor, remainder]}}匹配 value 对 divisor 取模等于 remainder 的数字字段。冷门,适合不加额外列做采样(每 100 条取一条)。
db.events.find({user_id: {$mod: [100, 0]}}) -- 1% 采样{$expr: {$gt: ["$a", "$b"]}}用聚合表达式比较同一文档的两个字段。MongoDB 查询里写"a 字段 > b 字段"的唯一方式。
db.orders.find({$expr: {$gt: ["$total_cents", "$paid_cents"]}})db.users.find({$expr: {$lt: ["$last_login", "$created_at"]}}) -- 数据异常排查{field: null} // null vs missing{field: null} 同时匹配显式为 null 的文档和字段缺失的文档。只匹配显式 null 要配 {$exists: true}。
⚠ 常见坑: 从 SQL 过来的人常被这条坑到,SQL 里 NULL 和"没这列"毫不相干。{field: {$type: "null"}} 只匹配显式 null,绝不匹配缺失。
db.users.find({deleted_at: null}) // null 或缺失db.users.find({deleted_at: {$type: "null"}}) // 仅显式 null{"a.b.c": value} // dot notation on nested点号查询嵌套文档内的字段。整条路径是单个字符串 key,要加引号。同样的点号路径上可建索引,加速嵌套查找。
db.users.find({"address.city": "Tokyo"})db.users.createIndex({"address.city": 1}){field: {$bitsAllSet / $bitsAnySet: mask}}位运算查询操作符按位匹配整数字段。$bitsAllSet 要求 mask 所有位都置 1;$bitsAnySet 至少一位。把权限标志打包进单个整数时很有用。
db.users.find({perms: {$bitsAllSet: 6}}) // 第 1、2 位都置位db.users.find({perms: {$bitsAnySet: [0, 3]}}) // 第 0 或第 3 位置位{loc: {$geoWithin: {$centerSphere: [[lng, lat], radiusRad]}}}匹配球面圆内的点。$centerSphere 接圆心和弧度半径(距离 ÷ 地球半径 6378.1 公里)。没地理索引也能用,但有 2dsphere 索引快得多。
db.places.find({loc: {$geoWithin: {$centerSphere: [[-73.97, 40.77], 5 / 6378.1]}}}) // 半径 5km{field: {$jsonSchema: {...}}}$jsonSchema 在查询里按 JSON Schema 校验文档,匹配符合(或取反,找违规)的文档。可复用集合校验器上挂的同一份 schema。
db.users.find({$nor: [{$jsonSchema: {required: ["email"], properties: {email: {bsonType: "string"}}}}]}) // 找违规文档{$text: {$search: "a -b \"exact phrase\""}}全文检索语法:空格分隔的词是 OR;前缀减号排除某词;双引号包住的是必须出现的精确短语。集合上要有 text 索引。
db.posts.find({$text: {$search: "mongodb tutorial -beginner"}})db.posts.find({$text: {$search: "\"aggregation pipeline\""}}) // 精确短语{$comment: "trace-id-123"}给查询挂任意注释,它会出现在 profiler、慢查询日志和 currentOp 里。生产上定位"哪段应用代码发的慢查询"最快的办法。
db.orders.find({status: "paid"}).comment("checkout-summary-v2")db.orders.find({$query: {status: "paid"}, $comment: "report-job"})cursor.hint({index: 1})强制查询计划器用指定索引,而不让它自己选。少用,通常是绕过计划器选了更差的计划,或给候选索引做基准。
⚠ 常见坑: hint 指了不存在或不适用的索引会直接报错,不会回退。hint 还会冻结计划:以后有更好的索引也会被忽略,直到你去掉 hint。
db.orders.find({region: "us", status: "paid"}).hint({region: 1, status: 1})db.orders.find({}).hint({$natural: 1}) // 强制按磁盘顺序扫find({}, {field1: 1, field2: 1, _id: 0})包含投影:只返回列出的字段。_id 默认带上,除非显式 0。除 _id 外不能混用包含和排除。
⚠ 常见坑: db.users.find({}, {name: 1, age: 0}) 服务端报错 "Cannot do exclusion on field age in inclusion projection"。选一个模式(_id 是唯一例外)。
db.users.find({}, {name: 1, email: 1, _id: 0})db.users.find({}, {password_hash: 0, ssn: 0}) -- 排除敏感字段find({}, {nested: {$elemMatch: {...}}})$elemMatch 投影只返回数组中第一个匹配的元素。常用于从商品下一堆 review 里抽出"那条匹配的 review"。
db.products.find({_id: pid}, {reviews: {$elemMatch: {user_id: uid}}})find({}, {array: {$slice: N}}) / $slice: [skip, limit]限制返回数组元素数。$slice: N 取前/后 N 个(负数取后);$slice: [skip, limit] 是数组分页。服务端完成,无额外 round-trip。
db.posts.find({}, {comments: {$slice: 10}}) -- 前 10 条评论db.posts.find({}, {comments: {$slice: [20, 10]}}) -- 跳 20 取 10find({}, {"array.$": 1})位置投影返回数组中第一个被 query filter 命中的元素。要求 filter 里有对应数组上的条件,否则不投影任何元素。
db.users.find({"scores.subject": "math"}, {"scores.$": 1})find({$text: {$search: "q"}}, {score: {$meta: "textScore"}})用 {$meta: "textScore"} 把全文检索相关度投到每条结果上。配 sort({score: {$meta: "textScore"}}) 排序时必填。
db.posts.find({$text: {$search: "mongodb tutorial"}}, {score: {$meta: "textScore"}}).sort({score: {$meta: "textScore"}})find({}, {computed: {$cond: [...]}}) // expressive projection4.4 起 find() 投影接受聚合表达式,不只是 0/1。不用切到 aggregate() 就能算派生字段($concat、$cond、$dateToString)。
db.users.find({}, {full: {$concat: ["$first", " ", "$last"]}, _id: 0})db.orders.find({}, {label: {$cond: [{$gte: ["$total", 100]}, "big", "small"]}})find({}, {field: {$meta: "indexKey"}})用 {$meta: "indexKey"} 投出满足查询所用的原始索引 key,纯调试用途,确认是哪个索引服务了这次读、命中了哪些 key 值。
db.orders.find({region: "us"}, {k: {$meta: "indexKey"}}).hint({region: 1})projection excludes _id only: {_id: 0}只写 {_id: 0} 的投影是排除与隐式全包含的唯一合法混用,返回除 _id 外的所有字段。$out / 导出时 _id 冲突很有用。
db.users.find({}, {_id: 0}) // 全字段去 _idaggregation $project: {_id: 0, keep: 1}聚合 $project 里可自由混用包含和排除(不像 find 投影),因为每个字段都是独立表达式。同一阶段里既去掉 _id 又加计算字段。
db.orders.aggregate([{$project: {_id: 0, customer: 1, dollars: {$divide: ["$total_cents", 100]}}}])$match: {<filter>}过滤流过 pipeline 的文档。和 find() filter 等价,同样的操作符、同样吃索引。$match 越靠前越好,先把数据集缩小。
db.orders.aggregate([
{$match: {status: "paid", created_at: {$gte: lastMonth}}}
])$group: {_id: <expr>, <field>: {$sum/$avg/...: ...}}按 key 分组并应用累加器($sum、$avg、$min、$max、$push、$addToSet、$first、$last)。最强的阶段,等价于 SQL GROUP BY。
⚠ 常见坑: $group 默认每个分组都在内存里 buffer;1 亿文档会 OOM。aggregate() 调用传 {allowDiskUse: true} 让大分组溢写到磁盘。
db.orders.aggregate([
{$group: {_id: "$user_id", total: {$sum: "$total_cents"}, n: {$sum: 1}}}
])db.events.aggregate([
{$group: {_id: {y: {$year: "$at"}, m: {$month: "$at"}}, count: {$sum: 1}}}
], {allowDiskUse: true})$project: {<field>: <expr>, ...}重塑文档:包含、排除、改名、计算新字段。和 find() 投影不同,$project 支持完整聚合表达式($concat、$cond、$divide 等)。
db.orders.aggregate([
{$project: {customer: 1, dollars: {$divide: ["$total_cents", 100]}, _id: 0}}
])$lookup: {from, localField, foreignField, as}左外连接到另一个集合。把外部匹配文档作为数组挂到输入文档的 as 字段下。MongoDB 版的 SQL LEFT JOIN。
⚠ 常见坑: 外部集合的 foreignField 没索引就是 O(N*M),每条输入文档都全扫一次外部集合。foreignField 必须建索引。
db.orders.aggregate([
{$lookup: {from: "users", localField: "user_id", foreignField: "_id", as: "user"}},
{$unwind: "$user"}
])$unwind: "$arrayField"把数组每个元素展开成独立文档,数组 N 个元素就 fan-out 成 N 条文档。{preserveNullAndEmptyArrays: true} 保留空数组/缺失字段的文档。
db.orders.aggregate([{$unwind: "$items"}, {$group: {_id: "$items.sku", units: {$sum: "$items.qty"}}}])db.orders.aggregate([{$unwind: {path: "$items", preserveNullAndEmptyArrays: true}}])$sort: {<field>: 1|-1} / $limit / $skip标准排序、limit、skip 阶段。$sort 只有在非索引友好阶段(如 $group)之前时才能用上索引。top-N 用 $sort + $limit。
⚠ 常见坑: $skip 做深分页(skip: 100000)让服务端流式扫掉前面所有文档。改用唯一索引的范围过滤:{_id: {$gt: lastSeenId}}。
db.orders.aggregate([{$sort: {total_cents: -1}}, {$limit: 10}]) -- top 10$count: "fieldName"把 pipeline 输出替换成一条 {fieldName: N},里面是到此阶段的文档数。比 $group + $sum: 1 更快。
db.users.aggregate([{$match: {status: "active"}}, {$count: "activeUsers"}])$facet: {<name>: [<sub-pipeline>], ...}在同一输入集合上并行跑多个子 pipeline,返回一条包含所有结果的文档。一次 round-trip 出"搜索结果 + 各类目计数"。
db.products.aggregate([
{$match: {q}},
{$facet: {
page: [{$skip: 0}, {$limit: 20}],
by_category: [{$group: {_id: "$category", n: {$sum: 1}}}],
total: [{$count: "n"}]
}}
])$addFields / $set: {<field>: <expr>}添加或覆盖字段,不丢任何已有字段。功能与 $project 等价但只做包含,不需要把所有想保留的字段都列出来。
db.orders.aggregate([{$addFields: {dollars: {$divide: ["$total_cents", 100]}}}])db.orders.aggregate([{$set: {pricedAt: "$$NOW"}}])$bucket / $bucketAuto按显式数值区间分桶($bucket boundaries: [...])或自动均匀分桶($bucketAuto buckets: N)。画直方图的首选原语。
db.orders.aggregate([
{$bucket: {groupBy: "$total_cents", boundaries: [0, 1000, 10000, 100000, 1000000], default: "huge", output: {n: {$sum: 1}}}}
])$sum / $avg / $min / $max / $push / $addToSet$group 标准累加器。$sum / $avg 对常量 1 就是计数;$push 把每个值收集成数组;$addToSet 收集去重后的值。
db.orders.aggregate([{$group: {_id: "$user_id", spent: {$sum: "$total_cents"}, items: {$push: "$items"}, paid_methods: {$addToSet: "$payment_method"}}}])$cond: [<if>, <then>, <else>] / $switch内联条件表达式。$cond 是三目;$switch 处理多分支。两者都能用在任何聚合表达式里($project、$group 累加器参数、$match $expr)。
db.orders.aggregate([{$project: {tier: {$cond: [{$gte: ["$total_cents", 100000]}, "vip", "regular"]}}}])db.orders.aggregate([{$project: {tier: {$switch: {branches: [
{case: {$gte: ["$total_cents", 1000000]}, then: "diamond"},
{case: {$gte: ["$total_cents", 100000]}, then: "gold"}
], default: "regular"}}}}])$dateToString / $year / $month / $dayOfWeek日期格式化与抽取表达式。$dateToString 用 strftime 风格格式串;分量抽取返回整数,方便按年-月-日快速分组。
db.events.aggregate([{$project: {day: {$dateToString: {format: "%Y-%m-%d", date: "$at"}}}}])db.events.aggregate([{$group: {_id: {y: {$year: "$at"}, m: {$month: "$at"}}, n: {$sum: 1}}}])$merge / $out把 pipeline 结果落库。$out 原子替换整个集合(drop + rename);$merge 按目标 upsert,命中行为可配。物化视图的标准做法。
db.orders.aggregate([{$group: {_id: "$user_id", spent: {$sum: "$total_cents"}}}, {$out: "user_summary"}])db.orders.aggregate([..., {$merge: {into: "daily_revenue", on: "_id", whenMatched: "merge", whenNotMatched: "insert"}}])$setWindowFields: {partitionBy, sortBy, output}窗口函数(5.0+):在有序分区上算累计、移动平均、排名、lag/lead,等价于 SQL 的 OVER(PARTITION BY ... ORDER BY ...)。
db.sales.aggregate([{$setWindowFields: {partitionBy: "$region", sortBy: {date: 1}, output: {running: {$sum: "$amount", window: {documents: ["unbounded", "current"]}}}}}])$replaceRoot / $replaceWith: {newRoot}把嵌套子文档提升为新的顶层文档。$replaceWith 是 4.2+ 更简洁的别名。$lookup + $unwind 后把连接出的文档拍平到根,常这么用。
db.orders.aggregate([{$lookup: {from: "users", localField: "uid", foreignField: "_id", as: "u"}}, {$unwind: "$u"}, {$replaceWith: "$u"}])$unionWith: {coll, pipeline}把另一个集合的文档(可选先过一个子 pipeline)追加到当前流。等价于 SQL 里两个集合的 UNION ALL。
db.orders_2025.aggregate([{$unionWith: {coll: "orders_2026"}}, {$group: {_id: "$region", total: {$sum: "$amount"}}}])$lookup with let + pipeline (correlated join)pipeline 形式的 $lookup 把外层文档字段绑进 let 变量,再用 $$ 引用它们对外部集合跑完整子 pipeline。支持多键连接和范围连接。
db.orders.aggregate([{$lookup: {from: "prices", let: {sku: "$sku", d: "$date"}, pipeline: [{$match: {$expr: {$and: [{$eq: ["$sku", "$$sku"]}, {$lte: ["$start", "$$d"]}]}}}], as: "price"}}])$graphLookup: {from, startWith, connectFromField, connectToField, as}集合内的递归图遍历,按 parent/child 或好友的好友的边走到可配置深度。一个阶段里构造出祖先链或可达集合。
db.employees.aggregate([{$graphLookup: {from: "employees", startWith: "$reports_to", connectFromField: "reports_to", connectToField: "_id", as: "chain", maxDepth: 5}}])$sortByCount: "$field"按字段 $group 再按 count 降序 $sort 的简写。每个去重值返回 {_id, count},最高频在前,"top 取值"最快的写法。
db.events.aggregate([{$sortByCount: "$event_type"}])db.orders.aggregate([{$match: {status: "paid"}}, {$sortByCount: "$region"}])$sample: {size: N}返回 N 条伪随机文档。当 N 相对集合很小时走高效随机 cursor,否则对随机 key 排序。
⚠ 常见坑: $sample 可能返回重复,N 大或接在其它阶段后时可能全扫。要可复现采样,改用对 _id 哈希取模的 $match。
db.products.aggregate([{$sample: {size: 10}}]) // 随机 10 个$redact: {$cond / $$DESCEND / $$PRUNE / $$KEEP}pipeline 内的字段级访问控制,逐层决定 $$KEEP(保留)、$$PRUNE(剪掉)或 $$DESCEND(下钻子文档)。实现按字段的行级安全。
db.docs.aggregate([{$redact: {$cond: [{$in: [userLevel, "$allowed"]}, "$$DESCEND", "$$PRUNE"]}}])$dateTrunc / $dateAdd / $dateDiff日期运算表达式(5.0+)。$dateTrunc 把日期向下取整到某单位(小时/天/周)用于分桶;$dateAdd/$dateDiff 加间隔或按选定单位算两日期差。
db.events.aggregate([{$group: {_id: {$dateTrunc: {date: "$at", unit: "day"}}, n: {$sum: 1}}}])db.subs.aggregate([{$project: {days: {$dateDiff: {startDate: "$start", endDate: "$end", unit: "day"}}}}])$ifNull / $coalesce-style fallback$ifNull:第一个参数非 null 且存在就返回它,否则返回兜底值。pipeline 里给可能缺失的字段补默认值的地道写法。
db.users.aggregate([{$project: {nickname: {$ifNull: ["$nickname", "$name"]}}}])db.orders.aggregate([{$project: {tax: {$ifNull: ["$tax_cents", 0]}}}])$map / $filter / $reduce // array expressionspipeline 里逐元素变换数组:$map 对每个元素套表达式,$filter 留下满足条件的元素,$reduce 把数组折叠成单值。
db.orders.aggregate([{$project: {prices: {$map: {input: "$items", as: "i", in: {$multiply: ["$$i.qty", "$$i.unit"]}}}}}])db.orders.aggregate([{$project: {total: {$reduce: {input: "$amounts", initialValue: 0, in: {$add: ["$$value", "$$this"]}}}}}])$densify / $fill // gap filling$densify(5.1+)插入缺失文档把数值/日期序列补全;$fill 用前值结转或线性插值回填 null 字段。组合起来把时序数据备好画图。
db.metrics.aggregate([{$densify: {field: "ts", range: {step: 1, unit: "hour", bounds: "full"}}}, {$fill: {sortBy: {ts: 1}, output: {value: {method: "locf"}}}}])db.<coll>.createIndex({field: 1})建单字段升序索引。数字是排序方向(1 升、-1 降),单字段索引两个方向行为相同。
db.users.createIndex({email: 1})db.orders.createIndex({created_at: -1})db.<coll>.createIndex({a: 1, b: -1, c: 1})建复合索引。顺序很重要,支持 a、a+b、a+b+c 的查询,但不支持单独 b 或单独 c。顺序按最常见查询设。
⚠ 常见坑: {region: 1, status: 1, created_at: -1} 这个复合索引能服务 region 单查或 region+status,但帮不到 status 单查,那种只能每个 region 扫一遍索引。
db.orders.createIndex({region: 1, status: 1, created_at: -1})createIndex({field: 1}, {unique: true})在被索引字段上强制唯一。配合 partialFilterExpression 可只对子集(如未删除的用户)强制唯一,不影响 null 软删模式。
⚠ 常见坑: 部分文档没有该字段时,唯一索引把"缺失"当成一个值,最多只允许一条文档缺该字段。要放过多条缺失文档用 sparse 或 partial。
db.users.createIndex({email: 1}, {unique: true})db.users.createIndex({email: 1}, {unique: true, partialFilterExpression: {deleted_at: null}})createIndex({field: 1}, {sparse: true})稀疏索引只对该字段存在的文档建索引,多数文档没此字段时节省空间。配 unique 即"存在就唯一"。
⚠ 常见坑: 稀疏索引不能用于按该字段排序,排序会静默漏掉缺该字段的文档。现代用 partial 索引替代,没这个坑。
db.users.createIndex({phone: 1}, {sparse: true})createIndex({field: 1}, {partialFilterExpression: {<filter>}})部分索引只对匹配 filter 的文档建索引。比 sparse 严格更强,可以按 "status = active" 或 "created_at > X" 建。
db.users.createIndex({email: 1}, {partialFilterExpression: {status: "active"}})db.orders.createIndex({user_id: 1}, {partialFilterExpression: {total_cents: {$gt: 0}}})createIndex({createdAt: 1}, {expireAfterSeconds: N})TTL 索引,MongoDB 后台 reaper 每 60 秒扫一次,按索引日期字段把超过 N 秒的文档删掉。
⚠ 常见坑: reaper 每 60 秒跑一次,TTL 是尽力而为:文档可能比过期时间多活近一分钟。另外字段必须是 BSON Date,字符串/数字会被静默忽略。
db.sessions.createIndex({created_at: 1}, {expireAfterSeconds: 3600}) -- 1h 后自动清db.events.createIndex({expires_at: 1}, {expireAfterSeconds: 0}) -- 用字段值作为绝对过期时刻createIndex({field: "text"}) / $text $searchtext 索引启用全文检索($text: $search),带词干提取、停用词、按语言的分析器。一个集合最多一个 text 索引。
db.posts.createIndex({title: "text", body: "text"})db.posts.find({$text: {$search: "mongodb tutorial"}}, {score: {$meta: "textScore"}}).sort({score: {$meta: "textScore"}})createIndex({loc: "2dsphere"}) / $nearGeoJSON 点/多边形/线的地理索引。支持 $near(最近优先)、$geoWithin(区域内)、$geoIntersects(几何相交)。
db.places.createIndex({loc: "2dsphere"})db.places.find({loc: {$near: {$geometry: {type: "Point", coordinates: [-73.97, 40.77]}, $maxDistance: 1000}}})createIndex({field: "hashed"})hash 索引主要做分片键,不论值分布如何都把写均匀分到各分片。无法服务该字段的范围查询。
db.events.createIndex({user_id: "hashed"})sh.shardCollection("app.events", {user_id: "hashed"})getIndexes() / dropIndex(name)列出某集合所有索引(名称、key、选项)或按名称删一个。建索引前先 getIndexes(),"几乎重复但不一样"的索引常常白浪费磁盘和内存。
db.users.getIndexes()
db.users.dropIndex("email_1")db.users.dropIndexes() -- 删全部,保留 _id_
explain("executionStats")展示 MongoDB 计划如何执行一条查询。看 stage: "IXSCAN"(好)还是 "COLLSCAN"(差),以及 keysExamined / docsExamined / nReturned 比例。
db.orders.find({user_id: x, status: "paid"}).explain("executionStats")db.orders.aggregate([...]).explain("executionStats")createIndex({a: 1, "$**": 1}) // wildcard index通配符索引({"$**": 1} 或 {"path.$**": 1})给每个字段(或某路径下每个字段)建索引,适合属性 key 不可预知、用户自定义的集合。一个索引覆盖任意单字段查询。
⚠ 常见坑: 通配符索引不支持多字段复合查询、跨多个通配字段的排序、也不能做分片键。还放大写:每次字段写都动索引。
db.products.createIndex({"attributes.$**": 1})db.products.find({"attributes.color": "red"}) // 命中通配索引createIndex({a: 1, b: 1}, {collation: {locale, strength}})collation 索引按语言规则(忽略大小写/重音)排序比较字符串,而非按原始字节。查询必须用与索引相同的 collation 才能用上它。
db.users.createIndex({name: 1}, {collation: {locale: "en", strength: 2}}) // 大小写不敏感db.users.find({name: "alice"}).collation({locale: "en", strength: 2})createIndex({a: 1}, {background: true}) // legacy flag4.2 之前 {background: true} 建索引不持集合级写锁。4.2 起所有建索引都走优化的混合方式,这个 flag 被接受但忽略。
db.orders.createIndex({user_id: 1}, {background: true}) // 4.2+ 已无差别createIndex({a: 1}, {name: "custom_name"})覆盖自动生成的索引名(字段_方向 拼接)。当长复合或深嵌套路径索引的默认名超过 127 字节上限时必须自定义。
db.events.createIndex({region: 1, type: 1, created_at: -1}, {name: "evt_main"})db.events.dropIndex("evt_main")createIndex({a: 1}, {hidden: true})隐藏索引(4.4+)照常随写维护,但对查询计划器不可见。先隐藏一个可疑索引来评估删它的影响,判断错了也不用付重建代价。
db.orders.hideIndex("region_1_status_1")db.orders.unhideIndex("region_1_status_1") // 判断错了秒级恢复reIndex() / collection.reIndexdrop 并重建集合上所有索引。WiredTiger 几乎用不到,它是修 MMAPv1 索引碎片用的。会持排他锁,期间集合不可用。
⚠ 常见坑: reIndex 会阻塞集合上所有操作直到完成,在现代 WiredTiger 上没好处。真要重建就逐个 drop + createIndex。
db.orders.reIndex() // 极少需要
createIndexes (batch) / index build progresscreateIndexes 一条命令建多个索引,共用一次集合扫描。长建索引用 db.currentOp() 跟进度,找 "createIndexes" op 和它的进度字段。
db.runCommand({createIndexes: "orders", indexes: [{key: {a: 1}, name: "a_1"}, {key: {b: -1}, name: "b_-1"}]})db.currentOp({"command.createIndexes": {$exists: true}})createIndex({a: 1}, {wildcardProjection: {...}}) // wildcard subset用 wildcardProjection 把通配索引限定到(或排除)特定路径,只给用户可控的属性子树建索引而非整个文档,保持索引小。
db.products.createIndex({"$**": 1}, {wildcardProjection: {attributes: 1}})rs.status()返回副本集健康:每个成员的状态(PRIMARY、SECONDARY、ARBITER、RECOVERING)、optime、lag、最后心跳。副本集上最重要的一条命令。
rs.status()
rs.status().members.map(m => ({name: m.name, state: m.stateStr, lag: m.optimeDate}))rs.initiate({...}) / rs.conf()rs.initiate 在刚启动的 mongod 上初始化副本集;rs.conf() 返回当前配置文档。改配置用 rs.reconfig(newCfg)。
rs.initiate({_id: "rs0", members: [
{_id: 0, host: "n1:27017"},
{_id: 1, host: "n2:27017"},
{_id: 2, host: "n3:27017"}
]})rs.add("host:port") / rs.remove("host:port")在运行中的副本集里加/删一个成员。加节点触发 initial sync,数据量大时用快照恢复,别走流式同步。
⚠ 常见坑: rs.add() 不指定 priority/votes 走默认。4 节点集就是偶数投票,加 arbiter 或某节点设 votes: 0,避免选主死锁。
rs.add("n4:27017")rs.add({host: "n4:27017", priority: 0, votes: 0, hidden: true}) -- 隐藏只读副本rs.remove("n4:27017")rs.stepDown(N) / rs.freeze(N)stepDown 让当前 PRIMARY 让位 N 秒,触发重选;freeze 让 SECONDARY 在 N 秒内不参选。计划内 failover 的关键。
rs.stepDown(60) -- 让位 60 秒
rs.freeze(120) -- 当前节点 120 秒内不参选
rs.printReplicationInfo() / rs.printSecondaryReplicationInfo()rs.printReplicationInfo() 显示 oplog 窗口(起、止、大小);rs.printSecondaryReplicationInfo() 显示各 secondary 的滞后秒数。用来定 oplog 大小和发现落后成员。
rs.printReplicationInfo()
rs.printSecondaryReplicationInfo()
db.getMongo().setReadPref("secondary"|"secondaryPreferred")把读请求路由到 secondary,适合把分析负载从主上挪走。代价:secondary 有 lag,读到的可能是旧数据。
⚠ 常见坑: "secondary" 模式所有 secondary 都不健康时硬失败;"secondaryPreferred" 会回退到 primary。关键读绝不用 secondary,最终一致性的惊喜会上生产。
db.getMongo().setReadPref("secondaryPreferred")db.orders.find().readPref("secondary") -- 单次查询级别writeConcern: {w: "majority", j: true, wtimeout: 5000}每次写的持久度。w: "majority" 等大多数投票节点 ack;j: true 等磁盘 journal 落盘;wtimeout 限制等待上限。
db.orders.insertOne(doc, {writeConcern: {w: "majority", j: true, wtimeout: 5000}})db.orders.updateOne(f, u, {writeConcern: {w: 1}}) -- 仅 primary ack,快但弱rs.reconfig(cfg, {force: true})应用新的副本集配置。{force: true} 在投票成员不足多数时也强推配置,丢失法定人数时的灾难恢复逃生口。
⚠ 常见坑: force 重配在被隔离的成员回来时可能造成双 primary,只在确定另一侧永久消失时用,并小心提升 config version。
cfg = rs.conf(); cfg.members = cfg.members.filter(m => m.host !== "dead:27017"); rs.reconfig(cfg, {force: true})rs.isMaster() / db.hello()hello()(isMaster 的更名替代)返回本节点角色、primary 地址、成员列表和最大 wire 版本,正是 driver 用来路由操作所读的内容。
db.hello().isWritablePrimary
db.hello().primary // 当前 primary 地址
readConcern: {level: "majority"|"linearizable"|"snapshot"}read concern 控制返回数据的一致性/新鲜度。"majority" 返回已被多数 ack 的数据(不会回滚);"linearizable" 保证最新;"snapshot" 在事务内读一致时间点。
⚠ 常见坑: "linearizable" 读必须打 primary 且可能慢,它要等一次空写确认主权。默认用 "majority";"linearizable" 留给读自己刚写的强一致需求。
db.orders.find({_id: x}).readConcern("majority")db.runCommand({find: "orders", filter: {_id: x}, readConcern: {level: "linearizable"}, maxTimeMS: 1000})tag sets + writeConcern: {w: "<customTag>"}副本集成员标签让你按数据中心或机架定义自定义写关注,w: "twoDataCenters" 等写到达两个 DC 标签的成员,实现跨地域持久。
cfg.settings.getLastErrorModes = {twoDC: {dc: 2}}; rs.reconfig(cfg)db.orders.insertOne(doc, {writeConcern: {w: "twoDC"}})rs.syncFrom("host:port")覆盖某 secondary 的复制源,而非自动选的同步源。用于让 initial sync 从更近/更空闲的节点拉,或打断不良同步链。
⚠ 常见坑: 这个覆盖不是永久的,副本集在下次重新评估同步源时可能改回自己的选择。它是提示,不是硬绑定。
rs.syncFrom("n2:27017")local.oplog.rs // tailing the oplogoplog 是 local 库里的 capped 集合,把每次写记成幂等操作。tail 它(更推荐用 change stream)驱动复制和 CDC 管道。
use local; db.oplog.rs.find().sort({$natural: -1}).limit(5)db.oplog.rs.stats().maxSize // oplog 容量上限
sh.status() / sh.status(true)打印分片集群摘要:shard 列表、已分片库、已分片集合及其分片键、每个 shard 的 chunk 分布。true 详细模式打印每个 chunk。
sh.status()
sh.status(true) -- 详细,每个 chunk
sh.enableSharding("dbname")在某库上启用分片。该库内任何集合分片前必须先启用。不会自己搬数据。
sh.enableSharding("myapp")sh.shardCollection("db.coll", {key: 1|"hashed"})按选定 key 分片集合。范围键(1)保留局部性、支持范围查询;hash 键把写均匀分布。分完不能改分片键(4.4+ refineCollectionShardKey 可扩展)。
⚠ 常见坑: 生产数据生成前选好分片键。低基数或单调递增的烂分片键造成热点 shard,修复痛苦,可能要 dump、drop、重新分片、再 load。
sh.shardCollection("myapp.events", {user_id: "hashed"})sh.shardCollection("myapp.orders", {region: 1, created_at: 1})sh.addShard("rs0/host:port,host:port")把一个副本集注册为集群的新 shard。参数是副本集连接串。balancer 会自动开始往新 shard 迁 chunk。
sh.addShard("rs1/n1:27018,n2:27018,n3:27018")sh.moveChunk("db.coll", {<shardKey>: v}, "shardName")手动把某 chunk 迁到指定 shard。绝大多数时候不用,balancer 会自动迁。只在 balancer 关了手动 rebalance 时用。
sh.moveChunk("myapp.orders", {region: "us-east"}, "shard0002")mongos --configdb <rs>/host:port,... / connect via mongosmongos 是查询路由,客户端连 mongos,不直连 shard。mongos 从 config-server 副本集获取"哪个 chunk 在哪个 shard"。
⚠ 常见坑: 直连单个 shard 绕过集群,没带分片键的写会路由错 shard,读只能看到该 shard 的部分数据。务必走 mongos。
mongos --configdb cfgrs/cfg1:27019,cfg2:27019,cfg3:27019
mongo --host mongos1:27017 -- 客户端连 mongos
sh.startBalancer() / sh.stopBalancer() / sh.getBalancerState()控制 chunk balancer。批量加载或维护窗口期间停掉它避免迁移开销,之后再开。getBalancerState() 报告当前是否启用。
sh.stopBalancer()
sh.startBalancer()
sh.getBalancerState() // true / false
sh.disableBalancing("db.coll") / sh.enableBalancing只对某一个集合关闭均衡,集群 balancer 对其它集合照常跑。用于把某个热点、手动调过的集合的 chunk 布局钉住。
sh.disableBalancing("myapp.sessions")sh.enableBalancing("myapp.sessions")sh.addShardToZone / sh.updateZoneKeyRange // zone shardingZone(标签感知)分片把某分片键范围钉到一组命名 zone 的 shard 上,比如把 EU 客户的 chunk 留在 EU 区域 shard 上满足数据驻留。balancer 会遵守 zone 边界。
sh.addShardToZone("shard0001", "EU")sh.updateZoneKeyRange("myapp.users", {region: "eu", _id: MinKey}, {region: "eu", _id: MaxKey}, "EU")refineCollectionShardKey("db.coll", {a: 1, b: 1})给已有分片键追加字段(4.4+)提高基数,不用从头重新分片,适合当初够用的 key 随数据增长变粗的情况。
⚠ 常见坑: 只能在已有 key 末尾追加字段,不能删、不能重排、不能改前缀。追加的新字段应建索引且后续文档要带上。
sh.shardCollection("myapp.events", {user_id: 1})db.adminCommand({refineCollectionShardKey: "myapp.events", key: {user_id: 1, created_at: 1}})reshardCollection("db.coll", {newKey})在线彻底更换集合分片键(5.0+)。MongoDB 按新 key 克隆数据、保持写入、再原子切换。烂分片键决策的解药。
⚠ 常见坑: reshard 期间需要第二份集合副本的磁盘空间,克隆期间增加写负载。先规划容量,挑低峰窗口跑。
db.adminCommand({reshardCollection: "myapp.orders", key: {customer_id: "hashed"}})targeted vs scatter-gather query带分片键(或其前缀)的查询是定向的,mongos 路由到持有那些 chunk 的单个 shard。不带分片键的查询是 scatter-gather,mongos 扇出到每个 shard。
⚠ 常见坑: scatter-gather 查询扩展性差,延迟取决于最慢的 shard,负载按 shard 数翻倍。把常用查询路径设计成始终带分片键。
db.orders.find({region: "us-east", _id: x}) // 定向(含分片键)db.orders.find({status: "paid"}) // scatter-gather(无分片键)$where: "this.a > this.b" — avoid$where 让每条文档跑一次 JS,比原生操作符慢几个量级,还有安全风险。用 $expr 配聚合表达式替代。
⚠ 常见坑: $where 还会让谓词用不上索引,每次查询都全表扫加每条文档跑 JS。很多托管 Mongo 服务出于安全直接禁用。
// BAD
db.orders.find({$where: "this.total > this.paid"})// GOOD
db.orders.find({$expr: {$gt: ["$total", "$paid"]}})Huge $in arrays (10k+ values)$in 数组 1 万+ 让"每个 key 走索引"的优化失效,每次匹配吃大量内存。分页 ID 或把 ID 落到中间集合走 $lookup。
// BAD
db.users.find({_id: {$in: [...10000_ids]}})// GOOD: page
for (let i = 0; i < ids.length; i += 1000) db.users.find({_id: {$in: ids.slice(i, i+1000)}}).forEach(...)Missing index — COLLSCAN in production上线前每条新查询都跑一次 explain("executionStats")。出现 COLLSCAN 且 docsExamined ≈ 集合大小,就是全表扫了一遍。
⚠ 常见坑: 生产上用 db.currentOp({secs_running: {$gt: 1}}) 抓正在跑的慢查询;mongotop 和 mongostat 看热集合。慢查询日志阈值 slowOpThresholdMs 默认 100 毫秒。
db.orders.find({user_id: x}).explain("executionStats") // 看 stagedb.currentOp({secs_running: {$gt: 1}})Unbounded $lookup — N*M scan$lookup 在外部集合 foreignField 没索引时是 O(N*M),每条输入都全扫一次外部集合。亚秒级查询在真实数据上变成几分钟。
db.users.createIndex({_id: 1}) // 默认已存在,但自定义 foreignField 务必显式建db.orders.createIndex({user_id: 1}) // foreignField 索引ObjectId vs string ID mismatchfind({_id: "65f1a2..."}) 匹配不到 _id 是 ObjectId("65f1a2...") 的文档,它们是不同的 BSON 类型。边界处用 ObjectId() 包一下字符串 ID。
⚠ 常见坑: driver 一般不会帮你转。Node: new ObjectId(stringId)。Python (pymongo): from bson import ObjectId; ObjectId(stringId)。忘了静默返回"无结果"。
// Node driver
db.collection("users").findOne({_id: new ObjectId(req.params.id)})Unanchored $regex — full collection scan{email: /alice/} 匹配 email 含 "alice" 的所有文档,但因正则没锚定开头,会全表扫。加 /^alice/ 才用得上索引。
// BAD: scans everything
db.users.find({email: /alice/})// GOOD: uses index
db.users.find({email: /^alice@/})writeConcern: {w: 0} — fire and forget{w: 0} 写入立刻返回,服务端是否收到都不确认。网络错误、重复键错、primary 崩溃全部静默丢失。
⚠ 常见坑: {w: 0} 只用于完全可丢的遥测。任何用户可见的写都该用 {w: "majority", j: true},单次更慢,但 primary failover 时不会静默丢数据。
db.metrics.insertOne(doc, {writeConcern: {w: 0}}) // 仅可丢遥测db.orders.insertOne(doc, {writeConcern: {w: "majority", j: true}}) // 用户数据Multi-doc transactions — last resort事务(4.0+ 副本集、4.2+ 分片集群)能用,但代价大:事务内每个读都加锁,争用飙升,任何 abort 都整块重试。优先重塑数据结构避免使用。
⚠ 常见坑: 单文档默认就原子。许多"事务"需求可以重塑为单条嵌套文档,一个 order 文档里装 items + payments,而不是分三集合,事务就消失了。
const session = db.getMongo().startSession()
session.startTransaction()
try { ... session.commitTransaction() } catch { session.abortTransaction() }Array equality {tags: ["a", "b"]} is order-sensitive查 {tags: ["a", "b"]} 只匹配 tags 恰好是 ["a", "b"] 这个顺序的文档,不匹配 ["b", "a"],也不匹配超集。要判成员用 $all(无序)或 $in(任一)。
⚠ 常见坑: 人人都至少踩一次,精确数组相等很少是你要的。{tags: {$all: ["a", "b"]}} 匹配任何同时包含两者的数组,不管顺序和多余元素。
// 精确相等(很少需要)
db.posts.find({tags: ["a", "b"]})// 包含两者(常用)
db.posts.find({tags: {$all: ["a", "b"]}})Number type coercion: int vs long vs doubleshell 默认把裸整数存成 double,所以 {n: 5} 可能匹配不到 driver 存的 32 位 int 字段。用 NumberInt() / NumberLong() 显式控制 BSON 类型。
⚠ 常见坑: 同一字段混用数字类型会破坏排序预期、可能让范围查询失效。写入时给每个字段定一种数字类型,用 $jsonSchema 校验器强制。
db.counters.insertOne({_id: "c", n: NumberLong(0)})db.counters.find({n: NumberInt(5)})Case-insensitive search without a collation index用 {$regex: "alice", $options: "i"} 做大小写不敏感匹配会全扫,i flag 让索引失效。改建 strength: 2 的 collation 索引,查询带相同 collation。
// BAD: 全扫
db.users.find({name: {$regex: "^alice$", $options: "i"}})// GOOD: collation 索引
db.users.createIndex({name: 1}, {collation: {locale: "en", strength: 2}})
db.users.find({name: "Alice"}).collation({locale: "en", strength: 2})Unbounded array growth in a document无限往数组里 push(一个文档上的评论、事件、日志行)最终撞到 16 MB BSON 文档上限,并拖慢该文档的每次读写。用 $slice 限长,或把数据挪到独立集合。
⚠ 常见坑: 远没到 16 MB,几 MB 的文档每次更新都整份加载重写。用"桶模式"(每个桶文档放 N 条)或独立集合让写保持便宜。
// 限长,避免无限增长
db.feeds.updateOne({_id: "f"}, {$push: {items: {$each: [x], $slice: -1000}}})Negation operators skip missing fields{field: {$ne: v}}、{$nin: [...]}、{$not: {...}} 都会匹配字段缺失的文档,因为"缺失"永远不等于 v。要求字段存在就配 {$exists: true}。
⚠ 常见坑: "找不在美国的用户" 写 {country: {$ne: "US"}} 会静默把所有没有 country 字段的用户也算进去。加 {country: {$exists: true, $ne: "US"}}。
// 可能误纳缺失字段
db.users.find({country: {$ne: "US"}})// 要求字段存在
db.users.find({country: {$exists: true, $ne: "US"}})skip/limit pagination drifts under concurrent writesfind().skip(n).limit(m) 每页都从头重数,翻页间的增删会移动窗口,用户看到重复或漏项。改用稳定唯一 key 上的范围分页。
⚠ 常见坑: 范围分页(seek/keyset),按 _id 排序的 {_id: {$gt: lastSeenId}},既在写入下稳定,每页也是 O(1) 而非 O(skip)。
// 漂移且越翻越慢
db.posts.find().sort({_id: 1}).skip(10000).limit(20)// 稳定且快
db.posts.find({_id: {$gt: lastId}}).sort({_id: 1}).limit(20)upsert race creates duplicates without a unique index同一 key 上两个并发 updateOne(..., {upsert: true}) 在没有唯一索引兜底时可能都插入,跨连接的"查再插"不原子。唯一索引让其中一个干净地失败。
⚠ 常见坑: upsert 的 key 务必配唯一索引。遇到重复键错误就重试,这次会找到另一个写入者插入的那条记录并改它。
db.counters.createIndex({name: 1}, {unique: true})db.counters.updateOne({name: "x"}, {$inc: {n: 1}}, {upsert: true})Forgotten allowDiskUse on large $sort / $grouppipeline 的 $sort 或 $group 超过 100 MB 内存上限会报 "Sort exceeded memory limit",除非传 {allowDiskUse: true}。6.0 起自动溢写磁盘,但老版本会硬失败。
db.events.aggregate([{$sort: {at: 1}}], {allowDiskUse: true})可搜索的 MongoDB 速查表,覆盖后端、DBA、SRE 日常真在 mongosh 里敲的 80+ 条命令,不是凑数入门篇。九大分类: shell 基础(use、show dbs、db.serverStatus、 db.runCommand、drop / dropDatabase 没二次确认的坑), CRUD(insertOne、insertMany ordered:false 加速、find 配 sort + limit、updateOne(头号 bug:忘了 $set 会把 整条文档静默替换掉)、updateMany、findOneAndUpdate returnDocument:after、findOneAndDelete 做原子任务队列、 bulkWrite 异构批操作、countDocuments 与 estimatedDocumentCount 取舍),查询操作符($eq、$gt / $in 配 1 万+ 数组的警告、$nin / $ne 也匹配字段缺失的 坑、$regex 锚定与不锚定对索引的影响、$exists、 $elemMatch 把多个条件绑到同一个数组元素、$or / $and 含 JS 对象 key 冲突 bug、$expr 比较同文档两个字段), 投影(包含/排除 + _id 例外、$elemMatch 投影返回第一 个匹配元素、$slice 做数组分页、位置 $、$meta textScore),聚合($match 越靠前越好、$group 含所有 累加器配 allowDiskUse 处理大分组、$project 全表达式、 $lookup 强制要求外部 foreignField 索引、$unwind、 $sort + $limit + $skip 深分页 skip 的坑、$count、 $facet 一次 round-trip 出多结果 UI、$addFields、 $bucket 画直方图、$cond 与 $switch、日期表达式 $dateToString、$merge / $out 落物化视图),索引 (createIndex 单字段+复合配前缀顺序规则、unique 配 partialFilterExpression、sparse 与 partial 取舍、TTL 配 60 秒 reaper 延迟、text 索引一集合最多一个、 2dsphere $near、hashed 用作分片键、explain executionStats 看 IXSCAN 与 COLLSCAN),复制集 (rs.status、rs.initiate、rs.add / rs.remove 偶数投票 成员陷阱、rs.stepDown 做计划 failover、读偏好、 writeConcern w:majority),分片(sh.status、 sh.enableSharding、sh.shardCollection 含分片键选错难 修复的警告、sh.addShard、sh.moveChunk、mongos 是唯一 正确入口),以及坑(避用 $where、$in 数组过大、缺索 引靠 explain 抓、$lookup 无外部索引就是 O(N*M)、 ObjectId 与 string ID 类型不匹配静默返回空、不锚定 $regex 全表扫、writeConcern w:0 静默丢数据、重塑 schema 避免多文档事务)。每条都附中英说明、1-3 条 可直接粘到 mongosh 跑的真实例子、一行"常见坑"。搜索 框跨命令 / 说明 / 例子 / 坑四个字段一起搜,分类胶囊 缩范围。完全在浏览器里跑,不连任何 MongoDB,不上传, 不发任何网络请求。配合 PostgreSQL、Redis、SQL、Docker 速查覆盖整条技术栈。
把内容粘贴或拖入工具面板。
点击按钮,在浏览器内本地处理,文件不上传。
一键复制结果或下载到本地。
适合穿插在写代码、查问题、做 Review、上线前的小任务里。
这些入口会把当前任务接到更完整的工具链里。
你是值班 SRE,没空翻文档。先筛"复制集",用 rs.status() 确认新的 PRIMARY 已选出,再查写入为什么卡:应用用了 {w: 1},在 12 秒选举窗口里丢了 ack。writeConcern 那条 提醒你 w:majority 才是安全默认,也讲清你刚接受的吞吐 代价。你在下一次告警前把配置改完上线。
你是被慢接口拖住的后端。把 find().explain ("executionStats") 按 explain 那条粘进去看,发现走了 COLLSCAN,totalDocsExamined 是 120 万,nReturned 才 20。复合索引前缀顺序那条点明你建在 {status, createdAt} 上的索引没法单独支撑按 createdAt 排序。你补对索引,接口 回到 50 毫秒以内。
你是做复盘的技术负责人。updateOne 那条把 bug 讲得很死: {name: "x"} 不带 $set 就是整条文档替换,email、角色、 时间戳全丢且不报错。你把这条加进新人文档和 driver lint, 再写一份从 oplog 恢复的 runbook,让下一个人不会再踩。
你是参加设计评审的资深工程师。事务那条主张多数"事务" 需求其实是建模问题:order、items、payments 常该是一条 嵌套文档,单文档写天然原子,不花成本。你重塑 schema, 砍掉那个受 60 秒上限约束的多文档事务,只把跨账户转账留 作系统里唯一真正的事务。
updateOne(filter, {name: 'x'}) 不带 $set 是整条替换,会丢掉其它所有字段。改动永远包进 {$set: {...}}。
以为 {field: {$ne: v}} 能跳过某值就够了,它连字段缺失的文档也匹配。需要排除缺失时加上 {$exists: true}。
在可能缺失的字段上直接建 unique 索引,最多只能有一条缺该字段。用 partialFilterExpression(如 {email: {$exists: true}})让多条都能缺。
这份速查是单个静态页。你输入的搜索词只在浏览器内存里对内置命令数组做匹配,不离开标签页,不连任何 MongoDB,不上传,不发网络请求,也不写进 URL。一边输入一边开 DevTools 的 Network 面板看,零流量,所以堡垒机后面的集群和气隙网络里都能放心用。
做你这行的人, 还会一起用这些。