mongo底层有insert update replace upsert? upsert 会replace吗?
非常好的问题!我们来澄清 MongoDB 底层的核心操作。
MongoDB 底层核心操作
MongoDB 驱动程序(Driver Level)提供以下核心操作:
| 操作 | 方法 | 描述 |
|---|---|---|
| Insert | insertOne(), insertMany() |
只插入新文档,如果文档已存在(_id 冲突)会报错 |
| Update | updateOne(), updateMany() |
只更新匹配的文档,使用更新操作符($set, $inc 等) |
| Replace | replaceOne() |
替换整个匹配的文档(保留 _id) |
| Delete | deleteOne(), deleteMany() |
删除文档 |
| Upsert | 不是一个独立操作,是选项 | update() 或 replaceOne() 的 { upsert: true } 选项 |
关键澄清:upsert 是选项,不是独立操作
upsert 是 update() 和 replaceOne() 的一个选项:
JAVASCRIPT
// upsert 作为 update 的选项
db.collection.updateOne(
filter,
update, // 更新操作符
{ upsert: true } // 选项
)
// upsert 作为 replaceOne 的选项
db.collection.replaceOne(
filter,
replacement, // 完整的新文档
{ upsert: true } // 选项
)
问题回答:upsert 会 replace 吗?
这取决于你调用的是哪个方法:
情况1:使用 update() 的 upsert → 不会 replace
JAVASCRIPT
// 这是更新操作,不是替换
db.users.updateOne(
{ _id: 1 },
{ $set: { name: "Alice" } }, // 使用更新操作符
{ upsert: true }
)
行为:
- 如果文档存在:只更新
name字段 - 如果文档不存在:创建新文档,包含
_id: 1和name: "Alice" - 结果:不会替换整个文档,只修改指定字段
情况2:使用 replaceOne() 的 upsert → 会 replace
JAVASCRIPT
// 这是替换操作
db.users.replaceOne(
{ _id: 1 },
{ name: "Alice" }, // 新文档(注意:不包含 _id)
{ upsert: true }
)
行为:
- 如果文档存在:用新文档完全替换(只保留 _id)
- 如果文档不存在:插入新文档
{ _id: 1, name: "Alice" } - 结果:会替换整个文档
MongoDB 驱动层面源码示例
让我们看看 MongoDB Java 驱动的源码(简化):
JAVA
// UpdateOperation.java
public UpdateResult execute(WriteBinding binding) {
if (hasUpdateOperators()) {
// 使用更新操作符($set, $inc 等)
return updateWithOperators(binding);
} else {
// 没有更新操作符 -> 替换操作
return replaceDocument(binding);
}
}
private UpdateResult updateWithOperators(WriteBinding binding) {
BsonDocument updateDocument = getUpdateDocument();
// 底层调用 MongoDB 的 update 命令
return collection.updateOne(filter, updateDocument, options);
}
private UpdateResult replaceDocument(WriteBinding binding) {
BsonDocument replacement = getReplacement();
// 底层调用 MongoDB 的 update 命令,但带有特殊标志
return collection.replaceOne(filter, replacement, options);
}
Spring Data MongoDB 的映射
Spring Data 将操作映射到底层 MongoDB 操作:
| Spring Data 方法 | MongoDB 操作 | 是否支持 upsert |
|---|---|---|
mongoTemplate.save() |
replaceOne(..., { upsert: true }) |
是,强制 upsert |
mongoTemplate.updateFirst().upsert() |
updateOne(..., { upsert: true }) |
是,可选 |
mongoTemplate.replaceOne().upsert() |
replaceOne(..., { upsert: true }) |
是,可选 |
mongoTemplate.insert() |
insertOne() |
否,纯插入 |
底层协议:MongoDB Wire Protocol
在协议层面,update 和 replace 都是通过 OP_UPDATE 或 OP_MSG 消息实现的,区别在于 update 字段的内容:
JAVASCRIPT
// MongoDB Wire Protocol 的 update 命令
{
"update": "users",
"updates": [
{
"q": { "_id": 1 }, // 查询条件
"u": { "$set": { "name": "Alice" } }, // 更新操作符
"upsert": true, // upsert 选项
"multi": false
}
]
}
// MongoDB Wire Protocol 的 replace 命令
{
"update": "users",
"updates": [
{
"q": { "_id": 1 }, // 查询条件
"u": { "name": "Alice" }, // 完整的替换文档
"upsert": true, // upsert 选项
"multi": false
}
]
}
注意:MongoDB 协议中,replaceOne 实际上也是通过 update 命令实现的,只是 u 字段包含的是完整文档而不是更新操作符。
重要区别:更新操作符 vs 完整文档
MongoDB 如何区分是更新还是替换?
JAVASCRIPT
// 判断规则:
function isUpdateDocument(doc) {
// 如果文档的第一个键以 "$" 开头,则是更新操作符
return Object.keys(doc)[0].startsWith("$");
}
// 示例:
{ $set: { name: "Alice" } } // 更新操作符 -> update
{ $inc: { count: 1 } } // 更新操作符 -> update
{ name: "Alice" } // 普通文档 -> replace
{ _id: 1, name: "Alice" } // 普通文档 -> replace
实际测试验证
让我们用 MongoDB Shell 验证:
JAVASCRIPT
// 1. 创建集合
db.test.drop();
// 2. 使用 update 的 upsert(不会 replace)
db.test.updateOne(
{ _id: 1 },
{ $set: { name: "Alice", age: 25 } },
{ upsert: true }
);
// 结果:{ _id: 1, name: "Alice", age: 25 }
// 3. 添加额外字段
db.test.updateOne(
{ _id: 1 },
{ $set: { city: "NY" } }
);
// 结果:{ _id: 1, name: "Alice", age: 25, city: "NY" }
// 4. 使用 replaceOne 的 upsert(会 replace)
db.test.replaceOne(
{ _id: 1 },
{ name: "Bob" }, // 注意:没有 age 和 city
{ upsert: true }
);
// 结果:{ _id: 1, name: "Bob" } // age 和 city 被替换掉了!
在 Spring Data 中如何选择
JAVA
// 1. 使用 Update 对象 -> 更新操作
Update update = Update.update("name", "Alice").inc("count", 1);
UpdateResult result = mongoTemplate.updateFirst(
Query.query(Criteria.where("_id").is(1)),
update,
Person.class
).upsert(); // 底层是 update with upsert
// 2. 使用 save() -> 替换操作
Person person = new Person(1, "Alice");
mongoTemplate.save(person); // 底层是 replaceOne with upsert
// 3. 明确使用替换操作
Person replacement = new Person(1, "Bob");
mongoTemplate.replaceOne(
Query.query(Criteria.where("_id").is(1)),
replacement,
ReplaceOptions.empty().upsert() // 显式指定 upsert
);
总结
MongoDB 底层有:
insertOne(),insertMany(),updateOne(),updateMany(),replaceOne(),deleteOne(),deleteMany()upsert 不是独立操作:是
update()和replaceOne()的一个选项upsert 会 replace 吗?
- 如果你用
update()的 upsert:不会 replace,只更新指定字段 - 如果你用
replaceOne()的 upsert:会 replace,替换整个文档
- 如果你用
如何区分:看是否使用更新操作符($set, $inc 等)
- 有
$操作符 → 更新操作 → 不会 replace - 无
$操作符 → 替换操作 → 会 replace
- 有
Spring Data 的映射:
Update对象 → 更新操作save()或完整对象 → 替换操作
简单记忆:upsert 本身不决定是更新还是替换,它只是告诉 MongoDB"有就更新/替换,没有就插入"。决定更新还是替换的是你使用的操作类型。