$inc能自动初始化未存在的字段为0再递增,但易因拼写错误(如scroe)静默创建无效字段,建议用字段白名单校验或$exists:true前置验证;对非数字类型会报错;安全初始化需$setOnInsert+upsert;数值须统一整型避免精度问题;高并发下应防止单文档热点。

为什么$inc不能直接用于未初始化的字段?
当你对一个不存在的字段执行 $inc,MongoDB 会自动创建该字段并设为 0,再执行增量操作——这看似方便,但容易掩盖逻辑缺陷。比如在用户积分场景中,如果误把 score 字段写成 scroe,$inc 仍会静默创建错误字段,后续查不到数据却无报错。
- 务必确保字段名拼写正确,建议在应用层做字段白名单校验
- 若需强约束(如必须存在才更新),先用
$exists: true做前置查询或配合upsert: false -
$inc对非数字类型字段(如字符串、数组)会抛出Cannot increment with non-numeric value错误
如何安全地初始化并自增计数器?
典型场景是“首次访问即创建,之后每次+1”。直接用 updateOne + $inc 不够,因为并发写入可能导致重复初始化。推荐组合 $setOnInsert 和 $inc:
db.visits.updateOne(
{ _id: "page_home" },
{
$setOnInsert: { count: 0 },
$inc: { count: 1 }
},
{ upsert: true }
)
-
$setOnInsert只在 upsert 新建文档时生效,避免覆盖已有值 - 不要用
$set: { count: 0 }替代,否则会重置已有计数 - 如果业务要求严格原子性(如库存扣减),需配合唯一索引或使用
findAndModify防止竞态
$inc 的数值精度和性能要注意什么?
MongoDB 的 $inc 操作底层是原子的,但数值类型会影响行为:传整数(如 1)生成 Int32 或 Int64,传小数(如 0.1)生成 Double。混合类型会导致索引失效或聚合精度丢失。
- 计数器一律用整数,避免
$inc: { clicks: 1.0 }这类写法 - 高并发场景下,单个文档频繁更新会成为写入瓶颈,可考虑分片键设计或改用“桶式计数器”(如每小时一个文档)
- 对大集合执行
$inc时,注意是否命中索引——$inc本身不依赖索引,但查询条件必须走索引,否则全表扫描代价极高
怎么验证 $inc 是否真的生效了?
别只看返回的 modifiedCount,它只表示匹配文档中被修改的个数,而 $inc 即使值加 0(如 $inc: { n: 0 })也会返回 modifiedCount: 1。真正要看的是字段值变化:
- 用
find查原始值,再执行$inc,立刻find对比 - 开启 MongoDB 日志(
logLevel: 1)观察实际执行的 update 命令 - 驱动层面可启用
writeConcern: { w: "majority" }确保写入多数节点后再读,避免读到旧值
$inc 就可能从便利工具变成隐蔽 bug 的源头。


















