MongoDB: Atualização do documento
Última atualização: 2026-08-26
A atualização de documentos é uma das operações de gravação mais comuns no MongoDB — dominar o modificador update é fundamental para modificar dados.
Este curso oferece uma análise aprofundada de updateOne, updateMany e replaceOne, de vários modificadores de atualização ($set, $inc, $push, $pull), do comportamento de upsert e das garantias de atomicidade.
1. O que você vai aprender
- Principais diferenças entre updateOne, updateMany e replaceOne
- Atualizações de campos com $set / $unset / $inc / $mul / $rename
- Atualizações de array com $push / $pull / $addToSet / $pop
- opção “upsert” (inserir se não existir)
- Atomicidade garantida das operações de atualização
- Interpretação do valor de retorno (matchedCount / modifiedCount)
2. Uma história real sobre uma plataforma de comércio eletrônico
(1) Desafio: Problemas de concorrência na dedução de estoque
Alice é responsável pelo sistema de pedidos de uma empresa de comércio eletrônico, onde se deparou com um problema clássico de concorrência ao atualizar o estoque:
// ❌ Counterexample:Check First, Then Edit(Competitive Conditions)
app.post('/api/orders', async (req, res) => {
const product = await Product.findOne({ sku: 'PHONE-001' });
if (product.stock <= 0) {
return res.status(400).json({ error: 'Out of stock' });
}
// ⚠️ Concurrency Issues Here:Both requests were read stock=1
await Product.updateOne(
{ sku: 'PHONE-001' },
{ $inc: { stock: -1 } }
);
// Both requests were successfully deducted,As a result, the inventory became -1
});
(2) Soluções para atualizações atômicas no MongoDB
// ✅ Correct Example:Usage + Atomic Manipulation
app.post('/api/orders', async (req, res) => {
const result = await Product.updateOne(
{ sku: 'PHONE-001', stock: { $gt: 0 } }, // Key:Conditional Filtering
{ $inc: { stock: -1 } }
);
if (result.modifiedCount === 0) {
return res.status(400).json({ error: 'Out of stock' });
}
// Only the following was changed: 1 This document indicates success
});
(3) Receita
| Dimensão | Consultar e atualizar | Atualização atômica |
|---|---|---|
| Segurança de concorrência | ❌ Condições de corrida | ✅ Operações atômicas |
| Desempenho | ⚠️ Duas consultas | ⚡ Uma operação |
| Complexidade do código | Alta | Baixa |
3. updateOne: Atualizar um único documento
Explicação do conceito: updateOne é o método de atualização mais utilizado no MongoDB; ele identifica o primeiro documento com base nas condições de filtro e aplica a operação de atualização. É semelhante ao UPDATE ... SET ... WHERE ... do SQL, mas o MongoDB usa modificadores de atualização (como $set e $inc) para especificar o que deve ser alterado, em vez de substituir o documento inteiro. Esse design torna as atualizações parciais mais eficientes — apenas os campos alterados são modificados, em vez de reescrever o documento inteiro.
Como funciona: updateOne O fluxo de execução é o seguinte: Fase de correspondência (localização de documentos com base no filtro) → Fase de atualização (aplicação de modificadores de atualização) → Atualização do índice (caso os campos do índice sejam modificados) → Confirmação do Write Concern. Toda a operação é atômica para um único documento — não há nenhum estado intermediário em que “apenas metade dos campos seja atualizada”.
sequenceDiagram
participant App as Applications
participant Mongo as MongoDB
participant WT as WiredTiger
App->>Mongo: updateOne({ sku: "PHONE-001" }, { $set: { price: 699 } })
Mongo->>Mongo: Match filter (Using Indexes)
Mongo->>Mongo: Applications $set Edit
Mongo->>Mongo: Check whether the index needs to be updated
Mongo->>WT: Save the modified document
WT-->>Mongo: Confirm
Mongo-->>App: { matchedCount: 1, modifiedCount: 1 }
| Parâmetro | Tipo | Descrição |
|---|---|---|
filter |
Documento | Critérios de pesquisa (obrigatórios) |
update |
Documento | Operação de atualização (obrigatória; deve incluir um modificador) |
options |
Documento | upsert / writeConcern etc. (opcional) |
| Campo de retorno | Significado | Observações |
|---|---|---|
matchedCount |
Número de documentos correspondentes | Pode ser 0 |
modifiedCount |
Número real de documentos modificados | 0 se o valor for o mesmo e não tiver mudado |
upsertedCount |
Número de documentos inseridos por meio de upsert | Pode ser apenas 1 quando upsert: true |
(1) Sintaxe básica
// === updateOne Basic Usage ===
db.products.updateOne(
{ sku: "PHONE-001" }, // filter
{ $set: { price: 699.99 } } // update
);
// Return Results:
// {
// acknowledged: true,
// matchedCount: 1, // Number of matching documents
// modifiedCount: 1, // Number of documents modified
// upsertedCount: 0, // Number of inserted documents
// upsertedId: null // Inserted _id
// }
(2) Interpretação dos valores de retorno
const result = await Product.updateOne(
{ sku: 'PHONE-001' },
{ $set: { stock: 50 } }
);
result.acknowledged; // true(Write confirmed)
result.matchedCount; // 1(Found 1 matches)
result.modifiedCount; // 1(Actual Changes 1 items)
result.upsertedCount; // 0(Not inserted)
| Campo | Significado |
|---|---|
matchedCount |
Número de documentos que correspondem ao filtro |
modifiedCount |
Número real de documentos modificados |
upsertedCount |
Número de documentos inseridos por meio de upsert |
upsertedId |
_id inserido no documento |
(3) Tratamento de casos sem correspondência
const result = await Product.updateOne(
{ sku: 'NOT_EXIST' },
{ $set: { stock: 0 } }
);
print(result.matchedCount); // 0
print(result.modifiedCount); // 0
// No errors,Do not modify
▶ Exemplo 1: colocando o updateOne em prática
// === Edit a Single Field ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { price: 699.99 } }
);
// === Edit Multiple Fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{
$set: {
price: 699.99,
stock: 50,
lastUpdated: new Date()
}
}
);
// === Updating Nested Fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "specs.battery": "5000mAh" } }
);
// === mongoose Equivalent Notation ===
const result = await Product.updateOne(
{ sku: 'PHONE-001' },
{ $set: { price: 699.99, lastUpdated: new Date() } }
);
4. updateMany: Atualização em lote
Explicação do conceito: updateMany identifica todos os documentos que atendem aos critérios e aplica a operação de atualização de maneira uniforme a todos eles. Ao contrário do updateOne, que modifica apenas o primeiro resultado encontrado, o updateMany pode atualizar dezenas de milhares de documentos de uma só vez. Esse é o método principal para modificações em lote (como descontos em todo o site, remoção em massa de listagens e correção de dados).
Como funciona: updateMany Primeiro, é executada uma consulta para identificar todos os documentos que atendem aos critérios; em seguida, a operação de atualização é aplicada a cada documento, um por um. O processo de atualização não é transacional — se houver falha no meio do processo, os documentos que já foram atualizados não serão revertidos. Portanto, ao realizar atualizações em massa, é necessário considerar a execução em lotes e o tratamento de erros.
| Dimensão | atualização de um | atualização de vários |
|---|---|---|
| Intervalo de partidas | Primeira partida | Todas as partidas |
| Operações em lote | ❌ Individual | ✅ Em lote |
| Revertimento de transação | ❌ Não suportado | ❌ Não suportado |
| Casos de uso | Edições individuais | Descontos em lote, exclusões e reativações |
| Risco | Baixo | Moderado (impacto significativo devido a erro do usuário) |
(1) Sintaxe básica
// === updateMany Basic Usage ===
db.products.updateMany(
{ category: 'Electronics' }, // filter(Multiple matches)
{ $set: { discount: 0.1 } } // update(Batch Application)
);
// Return Results:
// {
// acknowledged: true,
// matchedCount: 250, // Match 250 items
// modifiedCount: 250, // Edit 250 items
// upsertedCount: 0
// }
(2) Observações sobre atualizações em lote
// ⚠️ updateMany Transaction rollback is not supported
// If it fails along the way,Changes that have already been made will not be rolled back.
// ⚠️ Bulk updates may lock the collection
// We recommend using batch size control:
const BATCH_SIZE = 1000;
let modified = 0;
let lastId = null;
while (true) {
const result = await Product.updateMany(
{
category: 'Electronics',
_id: { $gt: lastId }
},
{ $set: { onSale: true } },
{ limit: BATCH_SIZE } // mongoose option
);
if (result.modifiedCount === 0) break;
modified += result.modifiedCount;
}
▶ Exemplo 2: Guia prático para atualizações em lote
// === Apply 10% off to all Electronics products ===
db.products.updateMany(
{ category: 'Electronics' },
{ $mul: { price: 0.9 } }
);
// === Remove all expired products from the shelves ===
db.products.updateMany(
{ expiryDate: { $lt: new Date() } },
{ $set: { isActive: false } }
);
// === To everyone 5 Add tags to products with star ratings ===
db.products.updateMany(
{ rating: { $gte: 4.8 } },
{ $addToSet: { tags: 'top-rated' } }
);
5. replaceOne: Substituir o documento inteiro
Explicação do conceito: A diferença fundamental entre replaceOne e updateOne é que updateOne atualiza os campos especificados no modificador, preservando os campos não especificados; replaceOne, por outro lado, substitui completamente o conteúdo do documento, e os campos não especificados serão excluídos. Essa é uma das operações mais perigosas no MongoDB, e o uso indevido pode resultar em perda de dados.
Casos de uso: Use replaceOne somente quando for necessário reescrever um documento por completo (por exemplo, para migração de dados ou atualizações no formato do documento). Na maioria dos casos, use updateOne + $set para modificar apenas os campos necessários.
| Dimensão | updateOne + $set | replaceOne |
|---|---|---|
| Campo não especificado | ✅ Manter | ❌ Excluir |
| Atomicidade | ✅ Atomicidade de um único documento | ✅ Atomicidade de um único documento |
| Casos de uso | Modificação de campos selecionados | Reescrita de todo o documento |
| Risco | Baixo | Alto (perda de campo) |
(1) Diferença em relação a updateOne
// === updateOne:Modify only the specified fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { price: 699 } }
);
// Results:{ _id, sku, title, price: 699, stock, category, ... }(Keep the other fields)
// === replaceOne:Replace throughout the document ===
db.products.replaceOne(
{ sku: 'PHONE-001' },
{ sku: 'PHONE-001', title: 'New Phone', price: 799 }
);
// Results:{ _id, sku, title: 'New Phone', price: 799 }
// ⚠️ Other Fields(stock、category etc.)All Lost!
(2) Casos de uso para replaceOne
// ✅ Applicable:Completely rewrite the document
db.users.replaceOne(
{ _id: 'user_001' },
{
_id: 'user_001',
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
updatedAt: new Date()
}
);
// ❌ Not applicable:I just want to modify one field(use updateOne + $set)
6. Modificadores de atualização de campo
Explicação do conceito: Os modificadores de atualização constituem a sintaxe central das operações de atualização do MongoDB e definem como os campos dos documentos são modificados. Ao contrário do SET field = value do SQL, o MongoDB oferece um conjunto abrangente de modificadores — $set (definir valor), $unset (excluir campo), $inc (incrementar/decrementar), $mul (multiplicar), $rename (renomear), $min/$max (atualizações condicionais), $currentDate (hora atual) e $setOnInsert (definido apenas durante o upsert).
Como funciona: Os modificadores de atualização são aplicados de forma atômica no nível do documento — os efeitos de todos os modificadores são aplicados na íntegra ou não são aplicados de forma alguma. É possível usar vários modificadores em combinação (por exemplo, $set + $inc + $currentDate), mas não é possível aplicar vários modificadores ao mesmo campo.
graph TB
A[Update Modifier] --> B[Field Value Class<br/>$set/$unset/$inc/$mul]
A --> C[Field Name Class<br/>$rename]
A --> D[Conditional Update Class<br/>$min/$max]
A --> E[Time-Related<br/>$currentDate]
A --> F[upsertDedicated<br/>$setOnInsert]
style A fill:#cce5ff
| Modificador | Função | Exemplo | Cria um campo? |
|---|---|---|---|
$set |
Definir valor do campo | { $set: { price: 699 } } |
Criar campo caso ele não exista |
$unset |
Excluir campo | { $unset: { discount: "" } } |
Ignorar se o campo não existir |
$inc |
Incrementar/Decrementar o valor | { $inc: { stock: -1 } } |
Começar em 0 se o campo não existir |
$mul |
Multiplicação | { $mul: { price: 0.9 } } |
Começar por 0 se o campo não existir |
$rename |
Renomear campo | { $rename: { "stock": "qty" } } |
— |
$min |
Selecione o menor valor | { $min: { price: 500 } } |
Crie o campo caso ele não exista |
$max |
Selecione o valor maior | { $max: { price: 1000 } } |
Crie o campo caso ele não exista |
$currentDate |
Definir a hora atual | { $currentDate: { updatedAt: true } } |
Criar caso o campo não exista |
$setOnInsert |
Definido apenas para upsert | { $setOnInsert: { createdAt: new Date() } } |
Criar apenas na inserção |
(1) $set Definir o valor de um campo
// === Set Fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { stock: 50, isActive: true } }
);
// === Set Up Nested Fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "specs.battery": "5000mAh" } }
);
// === Setting Array Elements ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "tags.0": "5g", "tags.1": "amoled" } }
);
(2) $unset: Exclui um campo
// === Delete a Single Field ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $unset: { discount: "" } }
);
// === Delete Multiple Fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $unset: { discount: "", internalNotes: "" } }
);
// === Delete Nested Fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $unset: { "specs.battery": "" } }
);
(3) $inc Incremento
// === Inventory Write-Downs ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $inc: { stock: -1 } }
);
// === Page Views +1 ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $inc: { viewCount: 1 } }
);
// === Cumulative Score(Multiple fields)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $inc: { stock: -1, soldCount: 1, viewCount: 1 } }
);
(4) Multiplicação com $mul
// === Apply 10% off ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $mul: { price: 0.9 } }
);
// === Prices Have Doubled ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $mul: { price: 2 } }
);
(5) $rename: Renomear um campo
// === Rename Field ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $rename: { "stock": "inventory" } }
);
// stock → inventory
// === Renaming Nested Fields ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $rename: { "specs.battery": "specs.batteryCapacity" } }
);
(6) $min / $max: Obter o valor mínimo/máximo
// === $min:Update only during on-duty hours ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $min: { price: 500 } }
);
// If the current price > 500,Change to 500;Otherwise, no change
// === $max:Update only when the value is greater ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $max: { price: 1000 } }
);
(7) $currentDate define a data atual
// === Set the Current Time ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $currentDate: { lastModified: true } }
);
// === Set to Date Type ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $currentDate: { lastModified: { $type: "date" } } }
);
(8) $setOnInsert: Definir um campo durante uma operação de upsert
// === Only at upsert Set a default value upon insertion ===
db.products.updateOne(
{ sku: 'NEW-001' },
{
$set: { price: 599 },
$setOnInsert: { createdAt: new Date(), stock: 0 }
},
{ upsert: true }
);
// If inserted:{ sku: 'NEW-001', price: 599, createdAt: ..., stock: 0 }
// If updated:{ sku: 'NEW-001', price: 599 }(Do not set createdAt、stock)
▶ Exemplo 3: Guia prático para atualizar campos compostos
// === Update the order status after the payment is successful ===
db.orders.updateOne(
{ _id: orderId },
{
$set: {
status: 'paid',
paidAt: new Date(),
paymentMethod: 'credit_card'
},
$inc: { version: 1 }, // Optimistic Lock Version Number
$currentDate: { updatedAt: true }
}
);
// === Update the last login time after the user logs in ===
db.users.updateOne(
{ _id: userId },
{
$set: { lastLoginAt: new Date(), lastLoginIp: '192.168.1.1' },
$inc: { loginCount: 1 }
}
);
7. Modificadores de atualização de matrizes
Explicação do conceito: As matrizes são as estruturas de dados mais flexíveis nos documentos do MongoDB, mas atualizar elementos de uma matriz é mais complexo do que atualizar campos comuns. O MongoDB oferece modificadores dedicados para matrizes — $push (adicionar um elemento), $pull (excluir elementos correspondentes), $addToSet (adicionar sem duplicatas), $pop (excluir o primeiro e o último elementos), bem como os operadores de posicionamento $ e $[] para atualizar com precisão elementos específicos em uma matriz.
Como funciona: Os modificadores de matriz atuam sobre os próprios elementos da matriz, e não sobre o documento inteiro. A principal diferença entre $push e $addToSet é que $push adiciona elementos incondicionalmente (o que pode resultar em duplicatas), enquanto $addToSet verifica primeiro se um elemento já existe (para remover duplicatas). O operador de posicionamento $, quando usado com uma condição de filtro, localiza o “primeiro elemento da matriz que corresponde”; $[] atua sobre “todos os elementos da matriz”; e $[identifier] + arrayFilters realizam “atualizações em lote com base em condições”.
Filosofia de projeto: O MongoDB incentiva a incorporação de pequenas quantidades de dados associados em matrizes (como uma lista de avaliações de produtos ou tags de usuários), mas matrizes muito grandes (que excedam várias centenas de elementos) podem afetar o desempenho das consultas e atualizações. Para grandes quantidades de dados associados, recomenda-se o uso de coleções separadas com referências.
graph TB
A[Array Update Modifiers] --> B[Add an element<br/>$push / $addToSet]
A --> C[Delete Element<br/>$pull / $pop]
A --> D[Bulk Operations<br/>$each / $slice]
A --> E[Location Update<br/>$ / $[] / $[filter]]
style A fill:#cce5ff
| Modificador | Função | Remoção de duplicatas | Exemplo | Frequência de uso |
|---|---|---|---|---|
$push |
Adicionar elemento | ❌ | { $push: { tags: "new" } } |
⭐⭐⭐ |
$addToSet |
Remoção e adição de duplicatas | ✅ | { $addToSet: { tags: "new" } } |
⭐⭐ |
$pull |
Excluir elementos correspondentes | — | { $pull: { tags: "old" } } |
⭐⭐ |
$pop |
Remover o primeiro/último elemento | — | { $pop: { tags: 1 } } |
⭐ |
$each |
Adicionar em massa (em conjunto com $push) | — | { $push: { tags: { $each: [...] } } } |
⭐⭐ |
$slice |
Limitar o comprimento da matriz | — | { $push: { tags: { $each: [...], $slice: -5 } } } |
⭐⭐ |
$position |
Especificar posição de inserção | — | { $push: { tags: { $each: [...], $position: 0 } } } |
⭐ |
(1) $push: Adiciona um elemento a uma matriz
// === Add a Single Element ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $push: { tags: 'bestseller' } }
);
// === Add Multiple Elements($each)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $push: { tags: { $each: ['5g', 'amoled', 'fast-charging'] } } }
);
// === Limit the array size($slice + $position)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{
$push: {
tags: {
$each: ['new1', 'new2', 'new3'],
$slice: -5, // Keep only the last one 5 items
$position: 0 // Insert from the beginning
}
}
}
);
(2) O comando $pull exclui os elementos correspondentes
// === Delete a Specified Value ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pull: { tags: 'old-tag' } }
);
// === Delete all elements that meet the criteria ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pull: { tags: { $in: ['outdated1', 'outdated2'] } } }
);
(3) $addToSet: Adicionar elementos a um array removendo duplicatas
// === Add only elements that do not exist ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $addToSet: { tags: 'new-tag' } }
);
// If tags Included 'new-tag',Do not add again
// === Add multiple($each)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $addToSet: { tags: { $each: ['tag1', 'tag2'] } } }
);
(4) $pop remove o primeiro ou o último elemento
// === Delete the last element ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pop: { tags: 1 } }
);
// === Delete the first element ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pop: { tags: -1 } }
);
(5) Localização e atualização de elementos de uma matriz
// === Update via Position Index ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "tags.0": "updated-first-tag" } }
);
// === Through $ Positioning Symbol Update(The first matching element found)===
db.products.updateOne(
{ sku: 'PHONE-001', "reviews.userId": 'user_001' },
{ $set: { "reviews.$.helpful": 10 } }
);
// Found userId='user_001' Comments on,Set it to helpful Field
// === Batch Update Array Elements ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "reviews.$[].status": "approved" } }
);
// All Comments status → approved
(6) $[] Atualizar todos os elementos
// === Update all array elements ===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "reviews.$[].status": "approved" } }
);
// === Updating Array Elements Based on Conditions(arrayFilters)===
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "reviews.$[lowRating].flagged": true } },
{
arrayFilters: [{ "lowRating.rating": { $lt: 2 } }]
}
);
// Rating by tags only < 2 Comments on
▶ Exemplo 4: Atualizações práticas em matrizes
// === Scene:E-commerce Review System ===
// 1. Add a comment
db.products.updateOne(
{ sku: 'PHONE-001' },
{
$push: {
reviews: {
userId: 'user_001',
rating: 5,
content: 'Excellent phone!',
createdAt: new Date(),
helpful: 0
}
}
}
);
// 2. Delete a specific comment from a user
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $pull: { reviews: { userId: 'user_001' } } }
);
// 3. Flag low-rated reviews
db.products.updateOne(
{ sku: 'PHONE-001' },
{ $set: { "reviews.$[r].flagged": true } },
{ arrayFilters: [{ "r.rating": { $lt: 2 } }] }
);
// 4. Limit the maximum number of comments 100 items
db.products.updateOne(
{ sku: 'PHONE-001' },
{
$push: {
reviews: {
$each: [newReview],
$slice: -100
}
}
}
);
8. A opção upsert
Explicação do conceito: UPSERT = UPDATE + INSERT. Trata-se de um modo de gravação exclusivo do MongoDB — se o documento existir, ele é atualizado; se não existir, é inserido. Esse modo é fundamental em cenários de “gravação idempotente”: o resultado permanece consistente, independentemente de quantas vezes a operação seja executada. Cenários típicos incluem: registros de login de usuários (criados no primeiro login de cada dia, com atualizações subsequentes); carrinhos de compras (criados ao adicionar o primeiro item, com atualizações subsequentes na quantidade); e configurações (criadas na configuração inicial, com modificações subsequentes nos valores).
Como funciona: Quando upsert: true é definido, o MongoDB primeiro tenta encontrar documentos que correspondam ao filtro. Se for encontrado um documento correspondente, ele aplica o modificador de atualização (exatamente como um updateOne comum). Se nenhum documento correspondente for encontrado, ele combina as condições de igualdade do filtro com $set/$setOnInsert do modificador de atualização para inserir um novo documento. $setOnInsert só tem efeito durante a inserção e é ignorado durante as atualizações — essa é a melhor maneira de definir valores padrão.
graph TB
A[updateOne + upsert: true] --> B{filter Matching Documents?}
B -->|Yes| C[Apply $set update modifier]
B -->|No| D[Merge filter conditions + $set + $setOnInsert]
D --> E[Insert a New Document]
C --> F[Back matchedCount=1<br/>upsertedCount=0]
E --> G[Back matchedCount=0<br/>upsertedCount=1<br/>upsertedId=ObjectId]
style C fill:#d4edda
style E fill:#fff3cd
| Comportamento do upsert | contagem de correspondências | contagem de modificações | contagem de inserções | ID da inserção |
|---|---|---|---|---|
| Localizar e modificar | 1 | 0 ou 1 | 0 | nulo |
| Não encontrado, inserir | 0 | 0 | 1 | ObjectId(...) |
| Não encontrado, sem upsert | 0 | 0 | 0 | null |
(1) O que é um “upsert”?
upsert = atualização + inserção; se o registro existir, atualize-o; se não existir, insira-o.
graph TB
A[updateOne + upsert] --> B{The document exists?}
B -->|Yes| C[Execute $set update]
B -->|No| D[Insert a New Document<br/>Apply $set + filter fields]
style C fill:#d4edda
style D fill:#fff3cd
(2) Comportamento de “upsert”
// === upsert: false(Default)===
const result1 = await Product.updateOne(
{ sku: 'NEW-001' },
{ $set: { price: 599 } }
);
print(result1.matchedCount); // 0(No matches found)
print(result1.modifiedCount); // 0
print(result1.upsertedCount); // 0
// === upsert: true ===
const result2 = await Product.updateOne(
{ sku: 'NEW-001' },
{ $set: { price: 599 } },
{ upsert: true }
);
print(result2.upsertedCount); // 1(Insert 1 items)
print(result2.upsertedId); // ObjectId('...')
(3) $setOnInsert é definido apenas no momento da inserção
// === Complete upsert Pattern ===
db.products.updateOne(
{ sku: 'NEW-001' },
{
$set: { price: 599, updatedAt: new Date() },
$setOnInsert: { createdAt: new Date(), stock: 0, viewCount: 0 }
},
{ upsert: true }
);
// === Insert if not present:{ sku: 'NEW-001', price: 599, updatedAt: ..., createdAt: ..., stock: 0, viewCount: 0 }
// === Update if it exists:{ sku: 'NEW-001', price: 599, updatedAt: ..., createdAt: <Old value> }
▶ Exemplo 5: Um guia prático sobre UPSERT
// === User Login History upsert ===
db.user_logins.updateOne(
{
userId: 'user_001',
date: '2026-07-01'
},
{
$set: { lastLoginAt: new Date() },
$inc: { loginCount: 1 },
$setOnInsert: { firstLoginAt: new Date() }
},
{ upsert: true }
);
// Create a record for the first login of each day,Future Updates
// === Shopping Cart upsert ===
db.carts.updateOne(
{ userId: 'user_001' },
{
$set: { updatedAt: new Date() },
$inc: { totalItems: 2 }
},
{ upsert: true }
);
9. Melhores práticas para operações de atualização
Explicação do conceito: As melhores práticas para operações de atualização giram em torno de três princípios fundamentais: atomicidade (evitar condições de corrida), desempenho (reduzir idas e voltas na rede e tempos de retenção de bloqueios) e segurança (prevenir operações acidentais). Dentre esses, a atomicidade é o mais crítico — as operações de documento único do MongoDB são inerentemente atômicas, mas o padrão “consultar e depois atualizar” compromete essa garantia de atomicidade.
Como funciona: O MongoDB garante a atomicidade para operações de gravação em um único documento — uma operação updateOne ou é totalmente bem-sucedida ou falha completamente; não há um estado intermediário em que a atualização seja concluída apenas parcialmente. No entanto, operações entre documentos não garantem automaticamente a atomicidade (transações entre múltiplos documentos requerem a versão 4.0 ou posterior). Portanto, ao projetar um modelo de dados, você deve tentar colocar os dados relacionados no mesmo documento para aproveitar a atomicidade de um único documento.
graph TB
A[Best Practices for Updates] --> B[Atomicity<br/>filter + Atomic Modifiers]
A --> C[Performance<br/>bulkWrite + Index]
A --> D[Safety<br/>Return Value Checking + Version Control]
B --> B1[✅ Recommendations: filter Conditional Filtering<br/>{ sku, stock: { $gt: 0 } }]
B --> B2[❌ Avoid: Check First, Then Edit<br/>findOne + updateOne]
style B1 fill:#d4edda
style B2 fill:#f8d7da
| Práticas | Melhores práticas | Anti-padrões |
|---|---|---|
| Segurança de concorrência | filtro + operações atômicas | findOne + updateOne |
| Atualização em lote | bulkWrite + ordered: false | Loop updateOne |
| Controle de versão | $inc: { __v: 1 } | Sem número de versão |
| Tratamento de erros | Verificar matchedCount/modifiedCount | Ignorar o valor de retorno |
(1) Garantia de atomicidade
// ✅ Safety:filter + Atomic Manipulation
const result = await Product.updateOne(
{ sku: 'PHONE-001', stock: { $gt: 0 } },
{ $inc: { stock: -1 } }
);
// ❌ Unsafe:Check First, Then Edit(Competitive Conditions)
const product = await Product.findOne({ sku: 'PHONE-001' });
if (product.stock > 0) {
await Product.updateOne(
{ sku: 'PHONE-001' },
{ $inc: { stock: -1 } }
);
}
(2) Otimização de desempenho
Visão geral do conceito: A otimização de desempenho para operações de atualização gira em torno de três estratégias principais: reduzir as idas e voltas na rede (usando bulkWrite em vez de chamadas repetidas de updateOne), filtrar por campos de índice (para evitar varreduras completas da tabela) e evitar reescritas desnecessárias de documentos (atualizando apenas os campos que foram alterados). Dentre elas, bulkWrite proporciona a melhoria de desempenho mais significativa — enquanto 100 operações individuais de updateOne levam aproximadamente 10 segundos, uma única operação de bulkWrite leva apenas 0,1 segundo.
Como funciona: Cada chamada ao updateOne envolve uma viagem completa de ida e volta pela rede — o cliente envia uma solicitação → o servidor localiza o documento → o aplicativo é atualizado → o resultado é retornado. O bulkWrite combina mais de 100 operações em uma única solicitação de rede; o servidor executa todas as operações em ordem e retorna os resultados em um único lote. Além disso, o mecanismo de armazenamento WiredTiger utiliza o mecanismo MVCC ao atualizar documentos — se o tamanho do documento aumentar após a atualização e não houver espaço suficiente no local original, o documento é movido para um novo local, acionando atualizações em todas as entradas do índice. Portanto, minimizar alterações no tamanho do documento (como substituir $inc por $set, o que reescreve todo o campo numérico) também traz benefícios de desempenho.
| Estratégia de otimização | Melhoria de desempenho | Alterações no código | Nível de recomendação |
|---|---|---|---|
bulkWrite Alternativa ao loop updateOne |
10–100x | Médio | ⭐⭐⭐ |
| Filtragem por campo de índice | 10–1.000x | Baixa | ⭐⭐⭐ |
$inc Substituir e reescrever campos numéricos |
1,5–2x | Baixo | ⭐⭐ |
| Controle do tamanho do lote (1.000/lote) | 1,5–3x | Baixo | ⭐⭐ |
| Variação no tamanho do documento | 1,2–1,5x | Baixa | ⭐ |
// === Optimization 1:Batch updates instead of multiple individual updates ===
// ❌ Slow: 100 times updateOne
for (const item of items) {
await Product.updateOne({ sku: item.sku }, { $inc: { stock: -item.qty } });
}
// ✅ Fast: 1 times bulkWrite
await Product.bulkWrite(
items.map(item => ({
updateOne: {
filter: { sku: item.sku, stock: { $gte: item.qty } },
update: { $inc: { stock: -item.qty, soldCount: item.qty } }
}
})),
{ ordered: false }
);
// === Optimization 2:Filter by Index Field ===
// ✅ Indexed:db.products.updateOne({ sku: 'PHONE-001' }, ...)
// ⚠️ No index:db.products.updateOne({ title: 'Phone' }, ...)
(3) Tratamento de erros
// === UpdateResult Processing ===
async function updateProductStock(sku, qty) {
const result = await Product.updateOne(
{ sku, stock: { $gte: qty } },
{ $inc: { stock: -qty, soldCount: qty } }
);
if (result.matchedCount === 0) {
throw new Error(`Out of stock or item not available: ${sku}`);
}
if (result.modifiedCount === 0) {
throw new Error('Update Failed');
}
return result;
}
❓ Perguntas Frequentes
P: Qual é a diferença de desempenho entre
updateOneeupdateMany? R:updateManyatualiza vários documentos em uma única operação, o que é eficiente, mas bloqueia mais documentos. Para atualizações em lote, recomendamos usar obulkWritecom oordered: false, que é mais flexível do que oupdateMany.
P: O
$setgera um erro se um campo não existir? R: Não. O$setcria campos automaticamente (incluindo campos aninhados). Chamar o$unsetem um campo inexistente também não causa nenhum problema.
P: Como o _id é gerado durante uma operação UPSERT? R: Ele utiliza automaticamente o valor do campo _id declarado no filtro; se o filtro não incluir o _id, o MongoDB gera automaticamente um ObjectId.
P: O que devo fazer se houver perda de precisão com números de ponto flutuante
$inc? R: Os números de ponto flutuante$incpodem apresentar problemas de precisão. Recomendamos usar o tipoDecimal128(mongoose.Types.Decimal128) para cálculos precisos.
P: Como localizo elementos ao atualizar campos de matriz? R: Use o localizador
$(para encontrar o primeiro elemento correspondente) ou$[identifier]+arrayFilters(para atualizar vários elementos com base em condições).
P: Quando devo usar
replaceOneem vez deupdateOne? R: UseupdateOne+$setse quiser modificar apenas alguns campos; usereplaceOnepara reescrever o documento inteiro. OreplaceOnenão preserva os campos que não forem especificados; portanto, use-o com cautela.
📖 Resumo
- O
updateOneatualiza um único documento; oupdateManyatualiza vários documentos - replaceOne: Substitui o documento inteiro; os campos que não forem especificados serão perdidos
- Modificadores de campo: $set/$unset/$inc/$mul/$rename/$min/$max/$currentDate/$setOnInsert
- Modificadores de matriz: $push/$pull/$addToSet/$pop/$each/$slice/$position
- Opção
upsert: inserir se não estiver presente;$setOnInsertdefine o valor somente na inserção - Operações atômicas: condição de filtro + atualização atômica, para evitar condições de corrida
- bulkWrite: Desempenho ideal para atualizações em massa
📝 Exercícios
- Exercício básico (⭐): Use
updateOnepara alterar o preço do produto, o estoque e a data da última atualização. - Exercício básico (⭐): Use $push para adicionar três tags a um produto e use $addToSet para testar a remoção de duplicatas.
- Exercício avançado (⭐⭐): Use
bulkWritepara implementar a dedução do estoque por pedido (dedução atômica de vários itens) e lidar com situações em que o estoque é insuficiente. - Problema avançado (⭐⭐): Use
upsertpara implementar estatísticas diárias de login de usuários (crie o valor na primeira ocorrência e, em seguida, incremente-o nas ocorrências subsequentes). - Desafio (⭐⭐⭐): Implemente um recurso de fusão de carrinhos de compras para combinar os itens do carrinho temporário com o carrinho de compras do usuário, lidando com itens duplicados (por meio da agregação de quantidades).